jfs-components 0.0.78 → 0.0.84
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 +11 -0
- package/lib/commonjs/components/AppBar/AppBar.js +56 -6
- package/lib/commonjs/components/Attached/Attached.js +183 -0
- package/lib/commonjs/components/Card/Card.js +25 -2
- package/lib/commonjs/components/Checkbox/Checkbox.js +18 -2
- package/lib/commonjs/components/Drawer/Drawer.js +6 -1
- package/lib/commonjs/components/DropdownInput/DropdownInput.js +30 -6
- package/lib/commonjs/components/ExpandableCheckbox/ExpandableCheckbox.js +17 -11
- package/lib/commonjs/components/FormField/FormField.js +1 -14
- package/lib/commonjs/components/FullscreenModal/FullscreenModal.js +9 -7
- package/lib/commonjs/components/ListItem/ListItem.js +26 -24
- package/lib/commonjs/components/MessageField/MessageField.js +1 -13
- package/lib/commonjs/components/PaymentFeedback/PaymentFeedback.js +12 -9
- package/lib/commonjs/components/PlanComparisonCard/PlanComparisonCard.js +237 -0
- package/lib/commonjs/components/Slot/Slot.js +73 -0
- package/lib/commonjs/components/Spinner/Spinner.js +217 -0
- package/lib/commonjs/components/TextInput/TextInput.js +33 -18
- package/lib/commonjs/components/index.js +28 -0
- package/lib/commonjs/icons/components/IconArrowdown.js +19 -0
- package/lib/commonjs/icons/components/IconArrowup.js +19 -0
- package/lib/commonjs/icons/components/IconChevrondowncircle.js +19 -0
- package/lib/commonjs/icons/components/IconChevronleftcircle.js +19 -0
- package/lib/commonjs/icons/components/IconChevronrightcircle.js +19 -0
- package/lib/commonjs/icons/components/IconChevronupcircle.js +19 -0
- package/lib/commonjs/icons/components/IconOsnavback.js +19 -0
- package/lib/commonjs/icons/components/IconOsnavcenter.js +19 -0
- package/lib/commonjs/icons/components/IconOsnavhome.js +19 -0
- package/lib/commonjs/icons/components/IconOsnavtask.js +19 -0
- package/lib/commonjs/icons/components/IconSignin.js +19 -0
- package/lib/commonjs/icons/components/IconSignout.js +19 -0
- package/lib/commonjs/icons/components/index.js +132 -0
- package/lib/commonjs/icons/registry.js +2 -2
- package/lib/module/components/AppBar/AppBar.js +56 -6
- package/lib/module/components/Attached/Attached.js +178 -0
- package/lib/module/components/Card/Card.js +25 -2
- package/lib/module/components/Checkbox/Checkbox.js +18 -2
- package/lib/module/components/Drawer/Drawer.js +6 -1
- package/lib/module/components/DropdownInput/DropdownInput.js +30 -6
- package/lib/module/components/ExpandableCheckbox/ExpandableCheckbox.js +17 -11
- package/lib/module/components/FormField/FormField.js +3 -16
- package/lib/module/components/FullscreenModal/FullscreenModal.js +9 -7
- package/lib/module/components/ListItem/ListItem.js +26 -24
- package/lib/module/components/MessageField/MessageField.js +3 -15
- package/lib/module/components/PaymentFeedback/PaymentFeedback.js +13 -9
- package/lib/module/components/PlanComparisonCard/PlanComparisonCard.js +234 -0
- package/lib/module/components/Slot/Slot.js +68 -0
- package/lib/module/components/Spinner/Spinner.js +212 -0
- package/lib/module/components/TextInput/TextInput.js +34 -19
- package/lib/module/components/index.js +4 -0
- package/lib/module/icons/components/IconArrowdown.js +12 -0
- package/lib/module/icons/components/IconArrowup.js +12 -0
- package/lib/module/icons/components/IconChevrondowncircle.js +12 -0
- package/lib/module/icons/components/IconChevronleftcircle.js +12 -0
- package/lib/module/icons/components/IconChevronrightcircle.js +12 -0
- package/lib/module/icons/components/IconChevronupcircle.js +12 -0
- package/lib/module/icons/components/IconOsnavback.js +12 -0
- package/lib/module/icons/components/IconOsnavcenter.js +12 -0
- package/lib/module/icons/components/IconOsnavhome.js +12 -0
- package/lib/module/icons/components/IconOsnavtask.js +12 -0
- package/lib/module/icons/components/IconSignin.js +12 -0
- package/lib/module/icons/components/IconSignout.js +12 -0
- package/lib/module/icons/components/index.js +12 -0
- package/lib/module/icons/registry.js +2 -2
- package/lib/typescript/src/components/AppBar/AppBar.d.ts +12 -1
- package/lib/typescript/src/components/Attached/Attached.d.ts +64 -0
- package/lib/typescript/src/components/Card/Card.d.ts +9 -2
- package/lib/typescript/src/components/DropdownInput/DropdownInput.d.ts +3 -2
- package/lib/typescript/src/components/ListItem/ListItem.d.ts +16 -6
- package/lib/typescript/src/components/PaymentFeedback/PaymentFeedback.d.ts +5 -1
- package/lib/typescript/src/components/PlanComparisonCard/PlanComparisonCard.d.ts +66 -0
- package/lib/typescript/src/components/Slot/Slot.d.ts +52 -0
- package/lib/typescript/src/components/Spinner/Spinner.d.ts +45 -0
- package/lib/typescript/src/components/index.d.ts +4 -0
- package/lib/typescript/src/icons/components/IconArrowdown.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconArrowup.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconChevrondowncircle.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconChevronleftcircle.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconChevronrightcircle.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconChevronupcircle.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconOsnavback.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconOsnavcenter.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconOsnavhome.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconOsnavtask.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconSignin.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconSignout.d.ts +3 -0
- package/lib/typescript/src/icons/components/index.d.ts +12 -0
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +3 -2
- package/src/components/AppBar/AppBar.tsx +79 -12
- package/src/components/Attached/Attached.tsx +237 -0
- package/src/components/Card/Card.tsx +28 -1
- package/src/components/Checkbox/Checkbox.tsx +14 -2
- package/src/components/Drawer/Drawer.tsx +4 -0
- package/src/components/DropdownInput/DropdownInput.tsx +54 -20
- package/src/components/ExpandableCheckbox/ExpandableCheckbox.tsx +13 -9
- package/src/components/FormField/FormField.tsx +3 -19
- package/src/components/FullscreenModal/FullscreenModal.tsx +6 -3
- package/src/components/ListItem/ListItem.tsx +42 -25
- package/src/components/MessageField/MessageField.tsx +3 -18
- package/src/components/PaymentFeedback/PaymentFeedback.tsx +15 -8
- package/src/components/PlanComparisonCard/PlanComparisonCard.tsx +316 -0
- package/src/components/Slot/Slot.tsx +91 -0
- package/src/components/Spinner/Spinner.tsx +273 -0
- package/src/components/TextInput/TextInput.tsx +37 -19
- package/src/components/index.ts +4 -0
- package/src/icons/components/IconArrowdown.tsx +11 -0
- package/src/icons/components/IconArrowup.tsx +11 -0
- package/src/icons/components/IconChevrondowncircle.tsx +11 -0
- package/src/icons/components/IconChevronleftcircle.tsx +11 -0
- package/src/icons/components/IconChevronrightcircle.tsx +11 -0
- package/src/icons/components/IconChevronupcircle.tsx +11 -0
- package/src/icons/components/IconOsnavback.tsx +11 -0
- package/src/icons/components/IconOsnavcenter.tsx +11 -0
- package/src/icons/components/IconOsnavhome.tsx +11 -0
- package/src/icons/components/IconOsnavtask.tsx +11 -0
- package/src/icons/components/IconSignin.tsx +11 -0
- package/src/icons/components/IconSignout.tsx +11 -0
- package/src/icons/components/index.ts +12 -0
- package/src/icons/registry.ts +49 -1
|
@@ -129,10 +129,6 @@ function ExpandableCheckbox({
|
|
|
129
129
|
|
|
130
130
|
const rowGap =
|
|
131
131
|
(getVariableByName('checkboxItem/gap', modes) as number | null) ?? 8
|
|
132
|
-
const rowPaddingHorizontal =
|
|
133
|
-
(getVariableByName('checkboxItem/padding/horizontal', modes) as number | null) ?? 0
|
|
134
|
-
const rowPaddingVertical =
|
|
135
|
-
(getVariableByName('checkboxItem/padding/vertical', modes) as number | null) ?? 0
|
|
136
132
|
|
|
137
133
|
const labelColor =
|
|
138
134
|
(getVariableByName('checkboxItem/foreground', modes) as string | null) ?? '#1a1c1f'
|
|
@@ -163,12 +159,10 @@ function ExpandableCheckbox({
|
|
|
163
159
|
alignSelf: isExpanded ? 'stretch' : 'auto',
|
|
164
160
|
minWidth: 0,
|
|
165
161
|
flexDirection: 'row',
|
|
166
|
-
alignItems: 'flex-start',
|
|
162
|
+
alignItems: isExpanded ? 'flex-start' : 'center',
|
|
167
163
|
gap: rowGap,
|
|
168
|
-
paddingHorizontal: rowPaddingHorizontal,
|
|
169
|
-
paddingVertical: rowPaddingVertical,
|
|
170
164
|
}),
|
|
171
|
-
[isExpanded, rowGap
|
|
165
|
+
[isExpanded, rowGap]
|
|
172
166
|
)
|
|
173
167
|
|
|
174
168
|
const resolvedLabelStyle: TextStyle = useMemo(
|
|
@@ -180,6 +174,13 @@ function ExpandableCheckbox({
|
|
|
180
174
|
fontSize: labelFontSize,
|
|
181
175
|
lineHeight: labelLineHeight,
|
|
182
176
|
fontWeight: labelFontWeight,
|
|
177
|
+
// Android adds asymmetric font padding and top-aligns the glyph inside
|
|
178
|
+
// an inflated line box when `lineHeight` is set. That makes the centered
|
|
179
|
+
// checkbox look like it drops below the text. Disabling the extra
|
|
180
|
+
// padding + centering the glyph keeps the single-line label optically
|
|
181
|
+
// aligned with the checkbox. No-op on iOS / web.
|
|
182
|
+
includeFontPadding: false,
|
|
183
|
+
textAlignVertical: isExpanded ? 'top' : 'center',
|
|
183
184
|
}),
|
|
184
185
|
[
|
|
185
186
|
labelColor,
|
|
@@ -187,11 +188,14 @@ function ExpandableCheckbox({
|
|
|
187
188
|
labelFontSize,
|
|
188
189
|
labelLineHeight,
|
|
189
190
|
labelFontWeight,
|
|
191
|
+
isExpanded,
|
|
190
192
|
]
|
|
191
193
|
)
|
|
192
194
|
|
|
195
|
+
// Layer component modes first (e.g. Color Mode), then button defaults so
|
|
196
|
+
// Secondary / XS / Low always win unless a dedicated override prop is added.
|
|
193
197
|
const buttonModes = useMemo(
|
|
194
|
-
() => ({ ...
|
|
198
|
+
() => ({ ...modes, ...BUTTON_DEFAULT_MODES }),
|
|
195
199
|
[modes]
|
|
196
200
|
)
|
|
197
201
|
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import React, { useCallback, useMemo,
|
|
1
|
+
import React, { useCallback, useMemo, useState } from 'react'
|
|
2
2
|
import {
|
|
3
3
|
View,
|
|
4
4
|
Text,
|
|
5
|
-
Pressable,
|
|
6
5
|
TextInput as RNTextInput,
|
|
7
6
|
type StyleProp,
|
|
8
7
|
type TextInputProps as RNTextInputProps,
|
|
@@ -347,16 +346,6 @@ function FormField({
|
|
|
347
346
|
const [isFocused, setIsFocused] = useState(false)
|
|
348
347
|
const interactive = !isDisabled && !isReadOnly
|
|
349
348
|
|
|
350
|
-
// Ref to the native input so tapping anywhere in the input row (padding,
|
|
351
|
-
// leading/trailing gutters) focuses it on the FIRST tap — fixing the Android
|
|
352
|
-
// "two taps to open the keyboard" issue caused by the row intercepting the
|
|
353
|
-
// initial touch.
|
|
354
|
-
const inputRef = useRef<RNTextInput>(null)
|
|
355
|
-
const focusInput = useCallback(() => {
|
|
356
|
-
if (!interactive) return
|
|
357
|
-
inputRef.current?.focus()
|
|
358
|
-
}, [interactive])
|
|
359
|
-
|
|
360
349
|
// FormField States cascade — error > read only/disabled > active (focused) > idle.
|
|
361
350
|
// Disabled maps to "Read Only" since there is no dedicated disabled mode and
|
|
362
351
|
// the visual treatment is closest. This is only the DEFAULT — an explicit
|
|
@@ -552,11 +541,7 @@ function FormField({
|
|
|
552
541
|
</View>
|
|
553
542
|
)}
|
|
554
543
|
|
|
555
|
-
<
|
|
556
|
-
style={[inputRowStyle, inputStyle]}
|
|
557
|
-
onPress={focusInput}
|
|
558
|
-
accessible={false}
|
|
559
|
-
>
|
|
544
|
+
<View style={[inputRowStyle, inputStyle]}>
|
|
560
545
|
{processedLeading != null && (
|
|
561
546
|
<View
|
|
562
547
|
accessibilityElementsHidden
|
|
@@ -566,7 +551,6 @@ function FormField({
|
|
|
566
551
|
</View>
|
|
567
552
|
)}
|
|
568
553
|
<RNTextInput
|
|
569
|
-
ref={inputRef}
|
|
570
554
|
style={[inputTextStyles, inputTextStyle]}
|
|
571
555
|
value={value ?? ''}
|
|
572
556
|
onChangeText={handleChangeText}
|
|
@@ -594,7 +578,7 @@ function FormField({
|
|
|
594
578
|
{processedTrailing}
|
|
595
579
|
</View>
|
|
596
580
|
)}
|
|
597
|
-
</
|
|
581
|
+
</View>
|
|
598
582
|
|
|
599
583
|
{supportLabel != null && supportLabel !== '' && (
|
|
600
584
|
<SupportText
|
|
@@ -21,6 +21,7 @@ import Button from '../Button/Button'
|
|
|
21
21
|
import Disclaimer from '../Disclaimer/Disclaimer'
|
|
22
22
|
import IconButton from '../IconButton/IconButton'
|
|
23
23
|
import ActionFooter from '../ActionFooter/ActionFooter'
|
|
24
|
+
import Slot from '../Slot/Slot'
|
|
24
25
|
|
|
25
26
|
// ---------------------------------------------------------------------------
|
|
26
27
|
// Forced modes
|
|
@@ -351,7 +352,7 @@ function FullscreenModal({
|
|
|
351
352
|
footerContent = footer
|
|
352
353
|
} else if (primaryActionLabel) {
|
|
353
354
|
footerContent = (
|
|
354
|
-
<
|
|
355
|
+
<Slot layoutDirection="vertical" modes={modes}>
|
|
355
356
|
<Button
|
|
356
357
|
label={primaryActionLabel}
|
|
357
358
|
modes={modes}
|
|
@@ -359,7 +360,7 @@ function FullscreenModal({
|
|
|
359
360
|
{...(onPrimaryAction ? { onPress: onPrimaryAction } : {})}
|
|
360
361
|
/>
|
|
361
362
|
{disclaimer ? <Disclaimer disclaimer={disclaimer} modes={modes} /> : null}
|
|
362
|
-
</
|
|
363
|
+
</Slot>
|
|
363
364
|
)
|
|
364
365
|
}
|
|
365
366
|
|
|
@@ -373,6 +374,9 @@ function FullscreenModal({
|
|
|
373
374
|
showsVerticalScrollIndicator={false}
|
|
374
375
|
onScroll={onScroll}
|
|
375
376
|
scrollEventThrottle={16}
|
|
377
|
+
// Tap an input in the body and it focuses on the FIRST tap, even when
|
|
378
|
+
// the keyboard is already open (default 'never' eats that tap).
|
|
379
|
+
keyboardShouldPersistTaps="handled"
|
|
376
380
|
>
|
|
377
381
|
<View style={heroTextRegionStyle}>
|
|
378
382
|
<HeroText
|
|
@@ -407,7 +411,6 @@ function FullscreenModal({
|
|
|
407
411
|
const rootStyle: ViewStyle = { flex: 1, width: '100%', position: 'relative' }
|
|
408
412
|
const scrollViewStyle: ViewStyle = { flex: 1 }
|
|
409
413
|
const scrollContentStyle: ViewStyle = { flexGrow: 1 }
|
|
410
|
-
const footerColumnStyle: ViewStyle = { width: '100%', gap: 8 }
|
|
411
414
|
const fullWidthStyle: ViewStyle = { width: '100%' }
|
|
412
415
|
const closeButtonStyle: ViewStyle = { position: 'absolute', top: 12, right: 12 }
|
|
413
416
|
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
type PressableStateCallbackType,
|
|
12
12
|
} from 'react-native'
|
|
13
13
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
14
|
-
import IconCapsule from '../IconCapsule/IconCapsule'
|
|
15
14
|
import NavArrow from '../NavArrow/NavArrow'
|
|
16
15
|
import { usePressableWebSupport, type WebAccessibilityProps } from '../../utils/web-platform-utils'
|
|
17
16
|
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
|
|
@@ -21,8 +20,16 @@ type ListItemProps = {
|
|
|
21
20
|
title?: string;
|
|
22
21
|
supportText?: string;
|
|
23
22
|
showSupportText?: boolean;
|
|
24
|
-
leading
|
|
23
|
+
/** Leading slot (Figma "leading"). Omitted or `null` renders nothing. */
|
|
24
|
+
leading?: React.ReactNode | null;
|
|
25
25
|
supportSlot?: React.ReactNode;
|
|
26
|
+
/** Trailing slot (Figma "trailing"), e.g. `MoneyValue` or `Button`. Horizontal layout only. */
|
|
27
|
+
trailing?: React.ReactNode;
|
|
28
|
+
/**
|
|
29
|
+
* @deprecated Renamed to `trailing` for a symmetric `leading` / `trailing`
|
|
30
|
+
* slot API. Still honored for backward compatibility; `trailing` wins when
|
|
31
|
+
* both are provided. Will be removed in a future major version.
|
|
32
|
+
*/
|
|
26
33
|
endSlot?: React.ReactNode;
|
|
27
34
|
/** Whether to show the NavArrow on the far right (Horizontal layout only). Defaults to true. */
|
|
28
35
|
navArrow?: boolean;
|
|
@@ -46,9 +53,10 @@ type ListItemProps = {
|
|
|
46
53
|
const IS_IOS = Platform.OS === 'ios'
|
|
47
54
|
const PRESS_DELAY = IS_IOS ? 130 : 0
|
|
48
55
|
|
|
49
|
-
// Forced modes for the
|
|
50
|
-
// overridden by external modes. Frozen so identity is stable across
|
|
51
|
-
|
|
56
|
+
// Forced modes for the leading/trailing slots — `Context: 'ListItem'` can
|
|
57
|
+
// never be overridden by external modes. Frozen so identity is stable across
|
|
58
|
+
// renders. Applied to both slots so they cascade modes identically.
|
|
59
|
+
const SLOT_FORCED_MODES = Object.freeze({ Context: 'ListItem' })
|
|
52
60
|
|
|
53
61
|
// Pressed visual is applied on the host view through Pressable's style
|
|
54
62
|
// callback, so a scroll-cancelled touch never schedules a React render.
|
|
@@ -72,7 +80,7 @@ interface ListItemTokens {
|
|
|
72
80
|
}
|
|
73
81
|
|
|
74
82
|
function resolveListItemTokens(modes: Record<string, any>): ListItemTokens {
|
|
75
|
-
// Modes used to cascade into slot children (leading / supportSlot /
|
|
83
|
+
// Modes used to cascade into slot children (leading / supportSlot / trailing).
|
|
76
84
|
// We do NOT inject an `AppearanceBrand` default here: slot content such as
|
|
77
85
|
// Buttons or Badges carry their own intended appearance, so forcing one onto
|
|
78
86
|
// them would be surprising.
|
|
@@ -167,9 +175,11 @@ const verticalSupportTextOverride: TextStyle = { textAlign: 'center' }
|
|
|
167
175
|
* - **design-token driven styling** via `getVariableByName` and `modes`
|
|
168
176
|
*
|
|
169
177
|
* Wherever the Figma layer name contains "Slot", this component exposes a
|
|
170
|
-
* dedicated React "slot" prop
|
|
178
|
+
* dedicated React "slot" prop. The leading and trailing edges share a
|
|
179
|
+
* symmetric `leading` / `trailing` slot API:
|
|
180
|
+
* - Slot "leading" → `leading`
|
|
171
181
|
* - Slot "support text" → `supportSlot`
|
|
172
|
-
* - Slot "
|
|
182
|
+
* - Slot "trailing" → `trailing`
|
|
173
183
|
*
|
|
174
184
|
* @component
|
|
175
185
|
* @param {Object} props
|
|
@@ -177,9 +187,9 @@ const verticalSupportTextOverride: TextStyle = { textAlign: 'center' }
|
|
|
177
187
|
* @param {string} [props.title='Title'] - Primary title used in the horizontal layout.
|
|
178
188
|
* @param {string} [props.supportText='Support Text'] - Support text used in both layouts when `supportSlot` is not provided.
|
|
179
189
|
* @param {boolean} [props.showSupportText=true] - Toggles rendering of the support text in Horizontal layout.
|
|
180
|
-
* @param {React.ReactNode} [props.leading] - Optional leading
|
|
190
|
+
* @param {React.ReactNode|null} [props.leading] - Optional leading slot. Omitted or `null` renders nothing.
|
|
181
191
|
* @param {React.ReactNode} [props.supportSlot] - Optional custom slot used instead of the default support text block.
|
|
182
|
-
* @param {React.ReactNode} [props.
|
|
192
|
+
* @param {React.ReactNode} [props.trailing] - Optional trailing slot (Figma Slot "trailing"). Horizontal layout only.
|
|
183
193
|
* @param {boolean} [props.navArrow=true] - Whether to show NavArrow on the far right (Horizontal layout only).
|
|
184
194
|
* @param {Object} [props.modes={}] - Modes object passed to `getVariableByName` for all design tokens.
|
|
185
195
|
* @param {Function} [props.onPress] - When provided, the entire item becomes pressable (navigation variant).
|
|
@@ -208,6 +218,7 @@ function ListItemImpl({
|
|
|
208
218
|
showSupportText = true,
|
|
209
219
|
leading,
|
|
210
220
|
supportSlot,
|
|
221
|
+
trailing,
|
|
211
222
|
endSlot,
|
|
212
223
|
navArrow = true,
|
|
213
224
|
modes = EMPTY_MODES,
|
|
@@ -251,12 +262,15 @@ function ListItemImpl({
|
|
|
251
262
|
// Process leading slot to pass modes to children. Memoized on
|
|
252
263
|
// (leading, resolvedModes) so a parent re-render doesn't re-walk the tree.
|
|
253
264
|
const leadingElement = useMemo(() => {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
265
|
+
if (leading == null) return null
|
|
266
|
+
|
|
267
|
+
const processed = cloneChildrenWithModes(
|
|
268
|
+
React.Children.toArray(leading),
|
|
269
|
+
tokens.resolvedModes,
|
|
270
|
+
SLOT_FORCED_MODES
|
|
271
|
+
)
|
|
272
|
+
if (processed.length === 0) return null
|
|
273
|
+
|
|
260
274
|
return processed.length === 1 ? processed[0] : processed
|
|
261
275
|
}, [leading, tokens.resolvedModes])
|
|
262
276
|
|
|
@@ -269,15 +283,18 @@ function ListItemImpl({
|
|
|
269
283
|
return processed.length === 1 ? processed[0] : processed
|
|
270
284
|
}, [supportSlot, tokens.resolvedModes])
|
|
271
285
|
|
|
272
|
-
|
|
273
|
-
|
|
286
|
+
// `trailing` wins; `endSlot` is the deprecated alias kept for back-compat.
|
|
287
|
+
const trailingContent = trailing ?? endSlot
|
|
288
|
+
|
|
289
|
+
const processedTrailing = useMemo(() => {
|
|
290
|
+
if (!trailingContent) return null
|
|
274
291
|
const processed = cloneChildrenWithModes(
|
|
275
|
-
React.Children.toArray(
|
|
292
|
+
React.Children.toArray(trailingContent),
|
|
276
293
|
tokens.resolvedModes,
|
|
277
|
-
|
|
294
|
+
SLOT_FORCED_MODES
|
|
278
295
|
)
|
|
279
296
|
return processed.length === 1 ? processed[0] : processed
|
|
280
|
-
}, [
|
|
297
|
+
}, [trailingContent, tokens.resolvedModes])
|
|
281
298
|
|
|
282
299
|
const renderSupportContent = () => {
|
|
283
300
|
if (processedSupportSlot) return processedSupportSlot
|
|
@@ -354,7 +371,7 @@ function ListItemImpl({
|
|
|
354
371
|
if (layout === 'Horizontal') {
|
|
355
372
|
const innerContent = (
|
|
356
373
|
<View style={innerContentStyleArray}>
|
|
357
|
-
{leadingElement}
|
|
374
|
+
{leadingElement ?? null}
|
|
358
375
|
<View
|
|
359
376
|
style={{
|
|
360
377
|
flex: 1,
|
|
@@ -370,8 +387,8 @@ function ListItemImpl({
|
|
|
370
387
|
</Text>
|
|
371
388
|
{showSupportText && renderSupportContent()}
|
|
372
389
|
</View>
|
|
373
|
-
{
|
|
374
|
-
<View style={tokens.trailingWrapperStyle}>{
|
|
390
|
+
{processedTrailing ? (
|
|
391
|
+
<View style={tokens.trailingWrapperStyle}>{processedTrailing}</View>
|
|
375
392
|
) : null}
|
|
376
393
|
{navArrow && <NavArrow direction="Forward" modes={tokens.resolvedModes} />}
|
|
377
394
|
</View>
|
|
@@ -412,7 +429,7 @@ function ListItemImpl({
|
|
|
412
429
|
// Vertical layout — icon on top, support text/slot below
|
|
413
430
|
const verticalContent = (
|
|
414
431
|
<View style={verticalContentStyleArray}>
|
|
415
|
-
{leadingElement}
|
|
432
|
+
{leadingElement ?? null}
|
|
416
433
|
{renderSupportContent()}
|
|
417
434
|
</View>
|
|
418
435
|
)
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import React, { useCallback, useMemo,
|
|
1
|
+
import React, { useCallback, useMemo, useState } from 'react'
|
|
2
2
|
import {
|
|
3
3
|
View,
|
|
4
4
|
Text,
|
|
5
|
-
Pressable,
|
|
6
5
|
TextInput as RNTextInput,
|
|
7
6
|
type StyleProp,
|
|
8
7
|
type TextInputProps as RNTextInputProps,
|
|
@@ -297,15 +296,6 @@ function MessageField({
|
|
|
297
296
|
const [isFocused, setIsFocused] = useState(false)
|
|
298
297
|
const interactive = !isDisabled && !isReadOnly
|
|
299
298
|
|
|
300
|
-
// Ref to the native textarea so tapping anywhere in the (padded) textarea
|
|
301
|
-
// container focuses it on the FIRST tap, fixing the Android "two taps to
|
|
302
|
-
// open the keyboard" issue.
|
|
303
|
-
const inputRef = useRef<RNTextInput>(null)
|
|
304
|
-
const focusInput = useCallback(() => {
|
|
305
|
-
if (!interactive) return
|
|
306
|
-
inputRef.current?.focus()
|
|
307
|
-
}, [interactive])
|
|
308
|
-
|
|
309
299
|
const { modes: globalModes } = useTokens()
|
|
310
300
|
const baseModes = useMemo(
|
|
311
301
|
() => ({ ...globalModes, ...propModes }),
|
|
@@ -508,13 +498,8 @@ function MessageField({
|
|
|
508
498
|
</View>
|
|
509
499
|
)}
|
|
510
500
|
|
|
511
|
-
<
|
|
512
|
-
style={[textareaContainerStyle, textareaStyle]}
|
|
513
|
-
onPress={focusInput}
|
|
514
|
-
accessible={false}
|
|
515
|
-
>
|
|
501
|
+
<View style={[textareaContainerStyle, textareaStyle]}>
|
|
516
502
|
<RNTextInput
|
|
517
|
-
ref={inputRef}
|
|
518
503
|
multiline
|
|
519
504
|
value={currentValue}
|
|
520
505
|
onChangeText={handleChangeText}
|
|
@@ -529,7 +514,7 @@ function MessageField({
|
|
|
529
514
|
accessibilityHint={accessibilityHint}
|
|
530
515
|
style={[inputTextStyle, inputStyle]}
|
|
531
516
|
/>
|
|
532
|
-
</
|
|
517
|
+
</View>
|
|
533
518
|
|
|
534
519
|
{shouldShowCounter && (
|
|
535
520
|
<Text style={counterTextStyle} accessibilityElementsHidden>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import React, { type ReactNode
|
|
1
|
+
import React, { type ReactNode } from 'react'
|
|
2
2
|
import { View, Text, type ViewStyle, type TextStyle } from 'react-native'
|
|
3
3
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
4
4
|
import { useTokens } from '../../design-tokens/JFSThemeProvider'
|
|
5
5
|
import IconCapsule from '../IconCapsule/IconCapsule'
|
|
6
|
-
import { EMPTY_MODES } from '../../utils/react-utils'
|
|
6
|
+
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
|
|
7
7
|
|
|
8
8
|
export type PaymentFeedbackProps = {
|
|
9
9
|
/** Large heading text, typically a monetary value (e.g. "₹50,000") */
|
|
@@ -20,7 +20,11 @@ export type PaymentFeedbackProps = {
|
|
|
20
20
|
iconName?: string
|
|
21
21
|
/** Optional custom media slot that replaces the default IconCapsule */
|
|
22
22
|
renderMedia?: ReactNode
|
|
23
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Mode configuration for design tokens. Also drives the default
|
|
25
|
+
* IconCapsule's color — pass `AppearanceSystem: 'positive' | 'warning' |
|
|
26
|
+
* 'negative'` to render a green/orange/red capsule (defaults to `positive`).
|
|
27
|
+
*/
|
|
24
28
|
modes?: Record<string, any>
|
|
25
29
|
style?: ViewStyle
|
|
26
30
|
}
|
|
@@ -28,7 +32,7 @@ export type PaymentFeedbackProps = {
|
|
|
28
32
|
export default function PaymentFeedback({
|
|
29
33
|
title = '₹50,000',
|
|
30
34
|
subtitle = 'Payment successful',
|
|
31
|
-
body
|
|
35
|
+
body,
|
|
32
36
|
details = '18 March 2025, 4:15 pm\nTransaction ID: TXN121466784',
|
|
33
37
|
showDetails = true,
|
|
34
38
|
iconName = 'ic_confirm',
|
|
@@ -123,14 +127,17 @@ export default function PaymentFeedback({
|
|
|
123
127
|
textAlign: 'center',
|
|
124
128
|
}
|
|
125
129
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
// Cascade modes into a custom media slot (per the modes-cascade convention);
|
|
131
|
+
// any modes the consumer set on the slot child still take precedence.
|
|
132
|
+
const mediaContent =
|
|
133
|
+
renderMedia != null ? cloneChildrenWithModes(renderMedia, modes) : null
|
|
129
134
|
|
|
130
135
|
const defaultMedia = (
|
|
131
136
|
<IconCapsule
|
|
132
137
|
iconName={iconName}
|
|
133
|
-
|
|
138
|
+
// `positive` is the default; consumers override the capsule color by
|
|
139
|
+
// passing `AppearanceSystem` (or any other mode) via the `modes` prop.
|
|
140
|
+
modes={{ AppearanceSystem: 'positive', ...modes, 'Icon Capsule Size': 'L', Emphasis: 'High', 'Semantic Intent': 'System' }}
|
|
134
141
|
/>
|
|
135
142
|
)
|
|
136
143
|
|