jfs-components 0.1.30 → 0.1.33
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/CHANGELOG.md +29 -0
- package/D2C.md +118 -0
- package/lib/commonjs/components/CategoryCard/CategoryCard.js +264 -0
- package/lib/commonjs/components/CompareTable/CompareTable.js +140 -84
- package/lib/commonjs/components/ContentSheet/ContentSheet.js +28 -5
- package/lib/commonjs/components/CounterBadge/CounterBadge.js +78 -0
- package/lib/commonjs/components/FavoriteToggle/FavoriteToggle.js +180 -0
- package/lib/commonjs/components/HelloJioInput/HelloJioInput.js +287 -0
- package/lib/commonjs/components/ValueBackMetric/ValueBackMetric.js +219 -0
- package/lib/commonjs/components/index.js +35 -0
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +40095 -1
- package/lib/commonjs/design-tokens/figma-modes.generated.js +3 -0
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/CategoryCard/CategoryCard.js +258 -0
- package/lib/module/components/CompareTable/CompareTable.js +140 -84
- package/lib/module/components/ContentSheet/ContentSheet.js +29 -6
- package/lib/module/components/CounterBadge/CounterBadge.js +73 -0
- package/lib/module/components/FavoriteToggle/FavoriteToggle.js +174 -0
- package/lib/module/components/HelloJioInput/HelloJioInput.js +281 -0
- package/lib/module/components/ValueBackMetric/ValueBackMetric.js +214 -0
- package/lib/module/components/index.js +6 -1
- package/lib/module/design-tokens/Coin Variables-variables-full.json +40095 -1
- package/lib/module/design-tokens/figma-modes.generated.js +3 -0
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/CategoryCard/CategoryCard.d.ts +72 -0
- package/lib/typescript/src/components/CompareTable/CompareTable.d.ts +16 -1
- package/lib/typescript/src/components/ContentSheet/ContentSheet.d.ts +7 -1
- package/lib/typescript/src/components/CounterBadge/CounterBadge.d.ts +19 -0
- package/lib/typescript/src/components/FavoriteToggle/FavoriteToggle.d.ts +66 -0
- package/lib/typescript/src/components/HelloJioInput/HelloJioInput.d.ts +111 -0
- package/lib/typescript/src/components/ValueBackMetric/ValueBackMetric.d.ts +86 -0
- package/lib/typescript/src/components/index.d.ts +5 -0
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +7 -1
- package/scripts/extract-figma-modes.js +359 -0
- package/src/components/CategoryCard/CategoryCard.tsx +404 -0
- package/src/components/CompareTable/CompareTable.tsx +201 -101
- package/src/components/ContentSheet/ContentSheet.tsx +40 -11
- package/src/components/CounterBadge/CounterBadge.tsx +95 -0
- package/src/components/FavoriteToggle/FavoriteToggle.tsx +243 -0
- package/src/components/HelloJioInput/HelloJioInput.tsx +513 -0
- package/src/components/ValueBackMetric/ValueBackMetric.tsx +298 -0
- package/src/components/index.ts +5 -0
- package/src/design-tokens/Coin Variables-variables-full.json +40095 -1
- package/src/design-tokens/figma-modes.generated.ts +3 -0
- package/src/icons/registry.ts +1 -1
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useCallback,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
} from 'react'
|
|
8
|
+
import {
|
|
9
|
+
Platform,
|
|
10
|
+
Pressable,
|
|
11
|
+
View,
|
|
12
|
+
TextInput as RNTextInput,
|
|
13
|
+
type StyleProp,
|
|
14
|
+
type ViewStyle,
|
|
15
|
+
type TextStyle,
|
|
16
|
+
type TextInputProps as RNTextInputProps,
|
|
17
|
+
} from 'react-native'
|
|
18
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
19
|
+
import { useTokens } from '../../design-tokens/JFSThemeProvider'
|
|
20
|
+
import Icon from '../../icons/Icon'
|
|
21
|
+
import {
|
|
22
|
+
EMPTY_MODES,
|
|
23
|
+
cloneChildrenWithModes,
|
|
24
|
+
mergeRefs,
|
|
25
|
+
} from '../../utils/react-utils'
|
|
26
|
+
import GlassFill from '../../utils/GlassFill/GlassFill'
|
|
27
|
+
import IconButton from '../IconButton/IconButton'
|
|
28
|
+
import type { Modes } from '../../design-tokens'
|
|
29
|
+
|
|
30
|
+
const IS_WEB = Platform.OS === 'web'
|
|
31
|
+
const STATE_COLLECTION = 'HelloJioInput State'
|
|
32
|
+
|
|
33
|
+
/** Default IconButton modes matching the Figma send control. */
|
|
34
|
+
const DEFAULT_SEND_BUTTON_MODES = Object.freeze({
|
|
35
|
+
AppearanceBrand: 'Primary',
|
|
36
|
+
'Button / Size': 'S',
|
|
37
|
+
}) as Modes
|
|
38
|
+
|
|
39
|
+
const toNumber = (value: unknown, fallback: number): number => {
|
|
40
|
+
if (typeof value === 'number') {
|
|
41
|
+
return Number.isFinite(value) ? value : fallback
|
|
42
|
+
}
|
|
43
|
+
if (typeof value === 'string') {
|
|
44
|
+
const parsed = Number(value)
|
|
45
|
+
return Number.isFinite(parsed) ? parsed : fallback
|
|
46
|
+
}
|
|
47
|
+
return fallback
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const toFontWeight = (
|
|
51
|
+
value: unknown,
|
|
52
|
+
fallback: TextStyle['fontWeight']
|
|
53
|
+
): TextStyle['fontWeight'] => {
|
|
54
|
+
if (typeof value === 'number') return String(value) as TextStyle['fontWeight']
|
|
55
|
+
if (typeof value === 'string') return value as TextStyle['fontWeight']
|
|
56
|
+
return fallback
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type VisualState = 'Idle' | 'Active' | 'IdleJioPlus'
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Fallback values from the Figma `HelloJioInput` component when local token
|
|
63
|
+
* JSON has not been synced yet. Mode-driven `getVariableByName` wins when
|
|
64
|
+
* tokens resolve.
|
|
65
|
+
*/
|
|
66
|
+
const STATE_FALLBACKS: Record<
|
|
67
|
+
VisualState,
|
|
68
|
+
{
|
|
69
|
+
background: string
|
|
70
|
+
borderColor: string
|
|
71
|
+
labelColor: string
|
|
72
|
+
iconColor: string
|
|
73
|
+
}
|
|
74
|
+
> = {
|
|
75
|
+
Idle: {
|
|
76
|
+
background: '#f5f5f5',
|
|
77
|
+
borderColor: '#f5f5f5',
|
|
78
|
+
labelColor: '#545961',
|
|
79
|
+
iconColor: '#545961',
|
|
80
|
+
},
|
|
81
|
+
Active: {
|
|
82
|
+
background: '#ffffff',
|
|
83
|
+
borderColor: '#b5b5b5',
|
|
84
|
+
labelColor: '#24262b',
|
|
85
|
+
iconColor: '#24262b',
|
|
86
|
+
},
|
|
87
|
+
IdleJioPlus: {
|
|
88
|
+
background: '#f5f5f5',
|
|
89
|
+
borderColor: '#f5f5f5',
|
|
90
|
+
labelColor: '#545961',
|
|
91
|
+
iconColor: '#545961',
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type HelloJioInputProps = {
|
|
96
|
+
/** Placeholder shown when empty. Defaults to `"Ask me anything"`. */
|
|
97
|
+
placeholder?: string
|
|
98
|
+
/** Controlled value. Pair with `onChangeText`. */
|
|
99
|
+
value?: string
|
|
100
|
+
/** Uncontrolled initial value. Ignored when `value` is provided. */
|
|
101
|
+
defaultValue?: string
|
|
102
|
+
/** Called whenever the text changes. */
|
|
103
|
+
onChangeText?: (text: string) => void
|
|
104
|
+
/**
|
|
105
|
+
* Called when the user submits — either via the send button or the keyboard
|
|
106
|
+
* return key. Receives the current text. This is the primary action hook.
|
|
107
|
+
*/
|
|
108
|
+
onSubmit?: (text: string) => void
|
|
109
|
+
/**
|
|
110
|
+
* Leading icon name from the registry. Defaults to `"ic_hellojio"`.
|
|
111
|
+
* Ignored when `leading` is provided.
|
|
112
|
+
*/
|
|
113
|
+
leadingIconName?: string
|
|
114
|
+
/**
|
|
115
|
+
* Slot replacing the default leading icon (Figma Slot "start"). Receives
|
|
116
|
+
* `modes` recursively.
|
|
117
|
+
*/
|
|
118
|
+
leading?: React.ReactNode
|
|
119
|
+
/**
|
|
120
|
+
* Slot replacing the default send `IconButton` (Figma Slot "end").
|
|
121
|
+
* Pass `null` to hide the trailing control entirely.
|
|
122
|
+
*/
|
|
123
|
+
trailing?: React.ReactNode | null
|
|
124
|
+
/**
|
|
125
|
+
* Icon name for the default send button. Defaults to `"ic_send_message"`.
|
|
126
|
+
* Ignored when `trailing` is provided.
|
|
127
|
+
*/
|
|
128
|
+
sendIconName?: string
|
|
129
|
+
/**
|
|
130
|
+
* When `true`, renders the IdleJioPlus glass surface while unfocused.
|
|
131
|
+
* Focus still switches to the Active (solid white + border) state.
|
|
132
|
+
*/
|
|
133
|
+
jioPlus?: boolean
|
|
134
|
+
/** Disables typing and the send button. */
|
|
135
|
+
disabled?: boolean
|
|
136
|
+
/** Design token modes. Visual Idle/Active/IdleJioPlus is derived from focus + `jioPlus`. */
|
|
137
|
+
modes?: Modes
|
|
138
|
+
/** Container style override. */
|
|
139
|
+
style?: StyleProp<ViewStyle>
|
|
140
|
+
/** Text input style override. */
|
|
141
|
+
inputStyle?: StyleProp<TextStyle>
|
|
142
|
+
accessibilityLabel?: string
|
|
143
|
+
accessibilityHint?: string
|
|
144
|
+
testID?: string
|
|
145
|
+
onFocus?: RNTextInputProps['onFocus']
|
|
146
|
+
onBlur?: RNTextInputProps['onBlur']
|
|
147
|
+
} & Omit<
|
|
148
|
+
RNTextInputProps,
|
|
149
|
+
| 'style'
|
|
150
|
+
| 'value'
|
|
151
|
+
| 'defaultValue'
|
|
152
|
+
| 'onChangeText'
|
|
153
|
+
| 'onFocus'
|
|
154
|
+
| 'onBlur'
|
|
155
|
+
| 'placeholder'
|
|
156
|
+
| 'editable'
|
|
157
|
+
| 'onSubmitEditing'
|
|
158
|
+
>
|
|
159
|
+
|
|
160
|
+
interface HelloJioInputTokens {
|
|
161
|
+
containerStyle: ViewStyle
|
|
162
|
+
inputStyle: TextStyle
|
|
163
|
+
iconColor: string
|
|
164
|
+
iconSize: number
|
|
165
|
+
blurIntensity: number
|
|
166
|
+
useGlass: boolean
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function resolveHelloJioInputTokens(
|
|
170
|
+
modes: Modes,
|
|
171
|
+
visualState: VisualState
|
|
172
|
+
): HelloJioInputTokens {
|
|
173
|
+
const fallback = STATE_FALLBACKS[visualState]
|
|
174
|
+
|
|
175
|
+
const background =
|
|
176
|
+
(getVariableByName('helloJioInput/background', modes) as string | null) ??
|
|
177
|
+
fallback.background
|
|
178
|
+
const borderColor =
|
|
179
|
+
(getVariableByName('helloJioInput/border/color', modes) as string | null) ??
|
|
180
|
+
fallback.borderColor
|
|
181
|
+
const strokeWidth = toNumber(
|
|
182
|
+
getVariableByName('helloJioInput/strokeWidth', modes),
|
|
183
|
+
1
|
|
184
|
+
)
|
|
185
|
+
const gap = toNumber(getVariableByName('helloJioInput/gap', modes), 8)
|
|
186
|
+
const paddingLeft = toNumber(
|
|
187
|
+
getVariableByName('helloJioInput/padding/left', modes),
|
|
188
|
+
12
|
|
189
|
+
)
|
|
190
|
+
const paddingRight = toNumber(
|
|
191
|
+
getVariableByName('helloJioInput/padding/right', modes),
|
|
192
|
+
4
|
|
193
|
+
)
|
|
194
|
+
const paddingTop = toNumber(
|
|
195
|
+
getVariableByName('helloJioInput/padding/top', modes),
|
|
196
|
+
4
|
|
197
|
+
)
|
|
198
|
+
const paddingBottom = toNumber(
|
|
199
|
+
getVariableByName('helloJioInput/padding/bottom', modes),
|
|
200
|
+
4
|
|
201
|
+
)
|
|
202
|
+
const height = toNumber(getVariableByName('helloJioInput/height', modes), 36)
|
|
203
|
+
const radiusRaw = toNumber(
|
|
204
|
+
getVariableByName('helloJioInput/radius', modes),
|
|
205
|
+
99999
|
|
206
|
+
)
|
|
207
|
+
const borderRadius = radiusRaw >= 9999 ? 99999 : radiusRaw
|
|
208
|
+
|
|
209
|
+
const labelColor =
|
|
210
|
+
(getVariableByName('helloJioInput/label/color', modes) as string | null) ??
|
|
211
|
+
fallback.labelColor
|
|
212
|
+
const fontSize = toNumber(
|
|
213
|
+
getVariableByName('helloJioInput/fontSize', modes),
|
|
214
|
+
14
|
|
215
|
+
)
|
|
216
|
+
const lineHeight = toNumber(
|
|
217
|
+
getVariableByName('helloJioInput/lineHeight', modes),
|
|
218
|
+
18
|
|
219
|
+
)
|
|
220
|
+
const fontFamily =
|
|
221
|
+
(getVariableByName('helloJioInput/fontFamily', modes) as string | null) ??
|
|
222
|
+
'JioType Var'
|
|
223
|
+
const fontWeight = toFontWeight(
|
|
224
|
+
getVariableByName('helloJioInput/fontWeight', modes),
|
|
225
|
+
'400'
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
const iconColor =
|
|
229
|
+
(getVariableByName('helloJioInput/icon/color', modes) as string | null) ??
|
|
230
|
+
fallback.iconColor
|
|
231
|
+
const iconSize = toNumber(
|
|
232
|
+
getVariableByName('textInput/icon/size', modes) ??
|
|
233
|
+
getVariableByName('helloJioInput/icon/size', modes),
|
|
234
|
+
18
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
const blurMinimal = toNumber(getVariableByName('blur/minimal', modes), 29)
|
|
238
|
+
const blurIntensity = Math.max(0, Math.min(100, Math.round(blurMinimal)))
|
|
239
|
+
const useGlass = visualState === 'IdleJioPlus'
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
containerStyle: {
|
|
243
|
+
position: 'relative',
|
|
244
|
+
flexDirection: 'row',
|
|
245
|
+
alignItems: 'center',
|
|
246
|
+
// Glass keeps the container transparent so GlassFill shows through.
|
|
247
|
+
backgroundColor: useGlass ? 'transparent' : background,
|
|
248
|
+
borderColor,
|
|
249
|
+
borderWidth: strokeWidth,
|
|
250
|
+
borderStyle: 'solid',
|
|
251
|
+
borderRadius,
|
|
252
|
+
paddingLeft,
|
|
253
|
+
paddingRight,
|
|
254
|
+
paddingTop,
|
|
255
|
+
paddingBottom,
|
|
256
|
+
gap,
|
|
257
|
+
height,
|
|
258
|
+
overflow: 'hidden',
|
|
259
|
+
width: '100%',
|
|
260
|
+
},
|
|
261
|
+
inputStyle: {
|
|
262
|
+
flex: 1,
|
|
263
|
+
color: labelColor,
|
|
264
|
+
fontSize,
|
|
265
|
+
fontFamily,
|
|
266
|
+
fontWeight,
|
|
267
|
+
// iOS single-line TextInput + explicit lineHeight sits below center —
|
|
268
|
+
// same quirk TextInput works around. Omit on iOS.
|
|
269
|
+
...(Platform.OS === 'ios' ? null : { lineHeight }),
|
|
270
|
+
padding: 0,
|
|
271
|
+
margin: 0,
|
|
272
|
+
minHeight: lineHeight,
|
|
273
|
+
outlineStyle: 'none' as any,
|
|
274
|
+
outlineWidth: 0,
|
|
275
|
+
outlineColor: 'transparent',
|
|
276
|
+
},
|
|
277
|
+
iconColor,
|
|
278
|
+
iconSize,
|
|
279
|
+
blurIntensity,
|
|
280
|
+
useGlass,
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* `HelloJioInput` is the pill-shaped HelloJio prompt field: leading brand
|
|
286
|
+
* icon, single-line text input, and a gold send `IconButton` on the right.
|
|
287
|
+
*
|
|
288
|
+
* Visual state is derived automatically:
|
|
289
|
+
* - **Idle** — gray fill, gray label (default)
|
|
290
|
+
* - **Active** — white fill + border while focused
|
|
291
|
+
* - **IdleJioPlus** — frosted glass when `jioPlus` and unfocused
|
|
292
|
+
*
|
|
293
|
+
* @component
|
|
294
|
+
*/
|
|
295
|
+
const HelloJioInput = forwardRef<RNTextInput, HelloJioInputProps>(
|
|
296
|
+
function HelloJioInput(
|
|
297
|
+
{
|
|
298
|
+
placeholder = 'Ask me anything',
|
|
299
|
+
value,
|
|
300
|
+
defaultValue = '',
|
|
301
|
+
onChangeText,
|
|
302
|
+
onSubmit,
|
|
303
|
+
leadingIconName = 'ic_hellojio',
|
|
304
|
+
leading,
|
|
305
|
+
trailing,
|
|
306
|
+
sendIconName = 'ic_send_message',
|
|
307
|
+
jioPlus = false,
|
|
308
|
+
disabled = false,
|
|
309
|
+
modes: propModes = EMPTY_MODES,
|
|
310
|
+
style,
|
|
311
|
+
inputStyle,
|
|
312
|
+
accessibilityLabel,
|
|
313
|
+
accessibilityHint,
|
|
314
|
+
testID,
|
|
315
|
+
onFocus,
|
|
316
|
+
onBlur,
|
|
317
|
+
returnKeyType = 'send',
|
|
318
|
+
...rest
|
|
319
|
+
},
|
|
320
|
+
ref
|
|
321
|
+
) {
|
|
322
|
+
const { modes: globalModes } = useTokens()
|
|
323
|
+
const inputRef = useRef<RNTextInput>(null)
|
|
324
|
+
const [isFocused, setIsFocused] = useState(false)
|
|
325
|
+
const [isHovered, setIsHovered] = useState(false)
|
|
326
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue)
|
|
327
|
+
|
|
328
|
+
const isControlled = value !== undefined
|
|
329
|
+
const currentValue = isControlled ? value : uncontrolledValue
|
|
330
|
+
|
|
331
|
+
const visualState: VisualState = isFocused
|
|
332
|
+
? 'Active'
|
|
333
|
+
: jioPlus
|
|
334
|
+
? 'IdleJioPlus'
|
|
335
|
+
: 'Idle'
|
|
336
|
+
|
|
337
|
+
const modes = useMemo(
|
|
338
|
+
() => ({
|
|
339
|
+
...globalModes,
|
|
340
|
+
...propModes,
|
|
341
|
+
[STATE_COLLECTION]: visualState,
|
|
342
|
+
}),
|
|
343
|
+
[globalModes, propModes, visualState]
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
const sendButtonModes = useMemo(
|
|
347
|
+
() => ({ ...DEFAULT_SEND_BUTTON_MODES, ...modes }),
|
|
348
|
+
[modes]
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
const tokens = useMemo(
|
|
352
|
+
() => resolveHelloJioInputTokens(modes, visualState),
|
|
353
|
+
[modes, visualState]
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
const handleChangeText = useCallback(
|
|
357
|
+
(text: string) => {
|
|
358
|
+
if (!isControlled) setUncontrolledValue(text)
|
|
359
|
+
onChangeText?.(text)
|
|
360
|
+
},
|
|
361
|
+
[isControlled, onChangeText]
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
const handleSubmit = useCallback(() => {
|
|
365
|
+
if (disabled) return
|
|
366
|
+
onSubmit?.(currentValue)
|
|
367
|
+
}, [disabled, onSubmit, currentValue])
|
|
368
|
+
|
|
369
|
+
const handleFocus = useCallback(
|
|
370
|
+
(e: any) => {
|
|
371
|
+
setIsFocused(true)
|
|
372
|
+
onFocus?.(e)
|
|
373
|
+
},
|
|
374
|
+
[onFocus]
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
const handleBlur = useCallback(
|
|
378
|
+
(e: any) => {
|
|
379
|
+
setIsFocused(false)
|
|
380
|
+
onBlur?.(e)
|
|
381
|
+
},
|
|
382
|
+
[onBlur]
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
const leadingElement =
|
|
386
|
+
leading ??
|
|
387
|
+
(leadingIconName ? (
|
|
388
|
+
<Icon
|
|
389
|
+
name={leadingIconName}
|
|
390
|
+
size={tokens.iconSize}
|
|
391
|
+
color={tokens.iconColor}
|
|
392
|
+
/>
|
|
393
|
+
) : null)
|
|
394
|
+
|
|
395
|
+
const processedLeading = leadingElement
|
|
396
|
+
? cloneChildrenWithModes(React.Children.toArray(leadingElement), modes)
|
|
397
|
+
: null
|
|
398
|
+
|
|
399
|
+
const defaultTrailing = (
|
|
400
|
+
<IconButton
|
|
401
|
+
iconName={sendIconName}
|
|
402
|
+
modes={sendButtonModes}
|
|
403
|
+
onPress={handleSubmit}
|
|
404
|
+
disabled={disabled}
|
|
405
|
+
accessibilityLabel="Send"
|
|
406
|
+
/>
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
const trailingElement =
|
|
410
|
+
trailing === undefined
|
|
411
|
+
? defaultTrailing
|
|
412
|
+
: trailing === null
|
|
413
|
+
? null
|
|
414
|
+
: trailing
|
|
415
|
+
|
|
416
|
+
const processedTrailing = trailingElement
|
|
417
|
+
? cloneChildrenWithModes(React.Children.toArray(trailingElement), modes)
|
|
418
|
+
: null
|
|
419
|
+
|
|
420
|
+
const a11yLabel =
|
|
421
|
+
accessibilityLabel ?? (placeholder || 'HelloJio input')
|
|
422
|
+
|
|
423
|
+
const hoverStyle: ViewStyle =
|
|
424
|
+
isHovered && !disabled && !isFocused ? { opacity: 0.95 } : {}
|
|
425
|
+
|
|
426
|
+
const containerStyleArray: StyleProp<ViewStyle> = [
|
|
427
|
+
tokens.containerStyle,
|
|
428
|
+
hoverStyle,
|
|
429
|
+
disabled ? { opacity: 0.5 } : null,
|
|
430
|
+
style,
|
|
431
|
+
]
|
|
432
|
+
|
|
433
|
+
const glassOverlay =
|
|
434
|
+
(getVariableByName('helloJioInput/background', modes) as string | null) ??
|
|
435
|
+
STATE_FALLBACKS.IdleJioPlus.background
|
|
436
|
+
|
|
437
|
+
const inner = (
|
|
438
|
+
<>
|
|
439
|
+
{tokens.useGlass ? (
|
|
440
|
+
<GlassFill
|
|
441
|
+
tint="light"
|
|
442
|
+
intensity={tokens.blurIntensity}
|
|
443
|
+
overlayColor={glassOverlay}
|
|
444
|
+
androidTintWash={false}
|
|
445
|
+
/>
|
|
446
|
+
) : null}
|
|
447
|
+
{processedLeading ? (
|
|
448
|
+
<View
|
|
449
|
+
accessibilityElementsHidden
|
|
450
|
+
importantForAccessibility="no"
|
|
451
|
+
style={SLOT_CENTER}
|
|
452
|
+
>
|
|
453
|
+
{processedLeading}
|
|
454
|
+
</View>
|
|
455
|
+
) : null}
|
|
456
|
+
<RNTextInput
|
|
457
|
+
ref={mergeRefs(inputRef, ref)}
|
|
458
|
+
accessibilityLabel={a11yLabel}
|
|
459
|
+
accessibilityHint={accessibilityHint}
|
|
460
|
+
placeholder={isFocused ? '' : placeholder}
|
|
461
|
+
placeholderTextColor={tokens.inputStyle.color}
|
|
462
|
+
value={currentValue}
|
|
463
|
+
onChangeText={handleChangeText}
|
|
464
|
+
onFocus={handleFocus}
|
|
465
|
+
onBlur={handleBlur}
|
|
466
|
+
onSubmitEditing={handleSubmit}
|
|
467
|
+
returnKeyType={returnKeyType}
|
|
468
|
+
editable={!disabled}
|
|
469
|
+
style={[tokens.inputStyle, inputStyle]}
|
|
470
|
+
testID={testID}
|
|
471
|
+
{...rest}
|
|
472
|
+
/>
|
|
473
|
+
{processedTrailing ? (
|
|
474
|
+
<View
|
|
475
|
+
// Keep the send button in the a11y tree — it has its own label.
|
|
476
|
+
style={SLOT_CENTER}
|
|
477
|
+
>
|
|
478
|
+
{processedTrailing}
|
|
479
|
+
</View>
|
|
480
|
+
) : null}
|
|
481
|
+
</>
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
// Same Android focus rule as TextInput: never wrap native TextInput in
|
|
485
|
+
// Pressable on native (steals the first tap). Web keeps Pressable for
|
|
486
|
+
// hover + click-anywhere-to-focus.
|
|
487
|
+
if (IS_WEB) {
|
|
488
|
+
return (
|
|
489
|
+
<Pressable
|
|
490
|
+
style={containerStyleArray}
|
|
491
|
+
onHoverIn={() => setIsHovered(true)}
|
|
492
|
+
onHoverOut={() => setIsHovered(false)}
|
|
493
|
+
onPress={() => {
|
|
494
|
+
if (!disabled) inputRef.current?.focus()
|
|
495
|
+
}}
|
|
496
|
+
accessible={false}
|
|
497
|
+
disabled={disabled}
|
|
498
|
+
>
|
|
499
|
+
{inner}
|
|
500
|
+
</Pressable>
|
|
501
|
+
)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return <View style={containerStyleArray}>{inner}</View>
|
|
505
|
+
}
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
const SLOT_CENTER: ViewStyle = {
|
|
509
|
+
justifyContent: 'center',
|
|
510
|
+
alignItems: 'center',
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
export default React.memo(HelloJioInput)
|