jfs-components 0.0.73 → 0.0.77
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 +115 -6
- package/lib/commonjs/components/AccountCard/AccountCard.js +247 -0
- package/lib/commonjs/components/ActionFooter/ActionFooter.js +147 -82
- package/lib/commonjs/components/AppBar/AppBar.js +17 -11
- package/lib/commonjs/components/Avatar/Avatar.js +20 -0
- package/lib/commonjs/components/Badge/Badge.js +23 -0
- package/lib/commonjs/components/Button/Button.js +37 -0
- package/lib/commonjs/components/CardBankAccount/CardBankAccount.js +18 -2
- package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +40 -25
- package/lib/commonjs/components/Dropdown/Dropdown.js +214 -0
- package/lib/commonjs/components/DropdownInput/DropdownInput.js +542 -0
- package/lib/commonjs/components/FormField/FormField.js +328 -178
- package/lib/commonjs/components/IconButton/IconButton.js +20 -0
- package/lib/commonjs/components/Image/Image.js +26 -1
- package/lib/commonjs/components/LottieIntroBlock/LottieIntroBlock.js +150 -0
- package/lib/commonjs/components/LottiePlayer/LottiePlayer.js +116 -0
- package/lib/commonjs/components/LottiePlayer/LottiePlayer.web.js +82 -0
- package/lib/commonjs/components/LottiePlayer/loadNativeLottieView.js +74 -0
- package/lib/commonjs/components/LottiePlayer/loadWebLottieView.js +50 -0
- package/lib/commonjs/components/PageHero/PageHero.js +189 -0
- package/lib/commonjs/components/PoweredByLabel/PoweredByLabel.js +135 -0
- package/lib/commonjs/components/PoweredByLabel/finvu.png +0 -0
- package/lib/commonjs/components/RechargeCard/RechargeCard.js +32 -17
- package/lib/commonjs/components/Text/Text.js +40 -3
- package/lib/commonjs/components/Tooltip/Tooltip.js +34 -27
- package/lib/commonjs/components/index.js +67 -0
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/commonjs/icons/Icon.js +16 -0
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/commonjs/index.js +12 -0
- package/lib/commonjs/skeleton/Skeleton.js +234 -0
- package/lib/commonjs/skeleton/SkeletonGroup.js +140 -0
- package/lib/commonjs/skeleton/index.js +58 -0
- package/lib/commonjs/skeleton/shimmer-tokens.js +189 -0
- package/lib/commonjs/skeleton/useReducedMotion.js +64 -0
- package/lib/module/components/AccountCard/AccountCard.js +241 -0
- package/lib/module/components/ActionFooter/ActionFooter.js +146 -82
- package/lib/module/components/AppBar/AppBar.js +17 -11
- package/lib/module/components/Avatar/Avatar.js +19 -0
- package/lib/module/components/Badge/Badge.js +23 -0
- package/lib/module/components/Button/Button.js +37 -0
- package/lib/module/components/CardBankAccount/CardBankAccount.js +17 -2
- package/lib/module/components/CheckboxItem/CheckboxItem.js +41 -26
- package/lib/module/components/Dropdown/Dropdown.js +206 -0
- package/lib/module/components/DropdownInput/DropdownInput.js +536 -0
- package/lib/module/components/FormField/FormField.js +330 -180
- package/lib/module/components/IconButton/IconButton.js +20 -0
- package/lib/module/components/Image/Image.js +25 -1
- package/lib/module/components/LottieIntroBlock/LottieIntroBlock.js +144 -0
- package/lib/module/components/LottiePlayer/LottiePlayer.js +111 -0
- package/lib/module/components/LottiePlayer/LottiePlayer.web.js +77 -0
- package/lib/module/components/LottiePlayer/loadNativeLottieView.js +69 -0
- package/lib/module/components/LottiePlayer/loadWebLottieView.js +45 -0
- package/lib/module/components/PageHero/PageHero.js +183 -0
- package/lib/module/components/PoweredByLabel/PoweredByLabel.js +130 -0
- package/lib/module/components/PoweredByLabel/finvu.png +0 -0
- package/lib/module/components/RechargeCard/RechargeCard.js +33 -17
- package/lib/module/components/Text/Text.js +40 -3
- package/lib/module/components/Tooltip/Tooltip.js +34 -27
- package/lib/module/components/index.js +8 -1
- package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/module/icons/Icon.js +16 -0
- package/lib/module/icons/registry.js +1 -1
- package/lib/module/index.js +2 -1
- package/lib/module/skeleton/Skeleton.js +229 -0
- package/lib/module/skeleton/SkeletonGroup.js +133 -0
- package/lib/module/skeleton/index.js +6 -0
- package/lib/module/skeleton/shimmer-tokens.js +181 -0
- package/lib/module/skeleton/useReducedMotion.js +61 -0
- package/lib/typescript/src/components/AccountCard/AccountCard.d.ts +81 -0
- package/lib/typescript/src/components/ActionFooter/ActionFooter.d.ts +26 -21
- package/lib/typescript/src/components/Avatar/Avatar.d.ts +7 -1
- package/lib/typescript/src/components/Badge/Badge.d.ts +7 -1
- package/lib/typescript/src/components/Button/Button.d.ts +8 -1
- package/lib/typescript/src/components/CardBankAccount/CardBankAccount.d.ts +9 -2
- package/lib/typescript/src/components/CheckboxItem/CheckboxItem.d.ts +18 -2
- package/lib/typescript/src/components/Dropdown/Dropdown.d.ts +62 -0
- package/lib/typescript/src/components/DropdownInput/DropdownInput.d.ts +107 -0
- package/lib/typescript/src/components/FormField/FormField.d.ts +76 -19
- package/lib/typescript/src/components/IconButton/IconButton.d.ts +7 -1
- package/lib/typescript/src/components/Image/Image.d.ts +8 -1
- package/lib/typescript/src/components/LottieIntroBlock/LottieIntroBlock.d.ts +58 -0
- package/lib/typescript/src/components/LottiePlayer/LottiePlayer.d.ts +85 -0
- package/lib/typescript/src/components/LottiePlayer/LottiePlayer.web.d.ts +28 -0
- package/lib/typescript/src/components/LottiePlayer/loadNativeLottieView.d.ts +11 -0
- package/lib/typescript/src/components/LottiePlayer/loadWebLottieView.d.ts +11 -0
- package/lib/typescript/src/components/PageHero/PageHero.d.ts +79 -0
- package/lib/typescript/src/components/PoweredByLabel/PoweredByLabel.d.ts +70 -0
- package/lib/typescript/src/components/Text/Text.d.ts +31 -2
- package/lib/typescript/src/components/Tooltip/Tooltip.d.ts +13 -2
- package/lib/typescript/src/components/index.d.ts +8 -1
- package/lib/typescript/src/icons/Icon.d.ts +7 -1
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/skeleton/Skeleton.d.ts +60 -0
- package/lib/typescript/src/skeleton/SkeletonGroup.d.ts +78 -0
- package/lib/typescript/src/skeleton/index.d.ts +5 -0
- package/lib/typescript/src/skeleton/shimmer-tokens.d.ts +160 -0
- package/lib/typescript/src/skeleton/useReducedMotion.d.ts +15 -0
- package/package.json +11 -3
- package/src/components/AccountCard/AccountCard.tsx +376 -0
- package/src/components/ActionFooter/ActionFooter.tsx +152 -86
- package/src/components/AppBar/AppBar.tsx +25 -14
- package/src/components/Avatar/Avatar.tsx +26 -0
- package/src/components/Badge/Badge.tsx +27 -0
- package/src/components/Button/Button.tsx +40 -0
- package/src/components/CardBankAccount/CardBankAccount.tsx +29 -3
- package/src/components/CheckboxItem/CheckboxItem.tsx +65 -30
- package/src/components/Dropdown/Dropdown.tsx +331 -0
- package/src/components/DropdownInput/DropdownInput.tsx +819 -0
- package/src/components/FormField/FormField.tsx +542 -215
- package/src/components/IconButton/IconButton.tsx +27 -0
- package/src/components/Image/Image.tsx +25 -0
- package/src/components/LottieIntroBlock/LottieIntroBlock.tsx +202 -0
- package/src/components/LottiePlayer/LottiePlayer.tsx +145 -0
- package/src/components/LottiePlayer/LottiePlayer.web.tsx +94 -0
- package/src/components/LottiePlayer/loadNativeLottieView.tsx +87 -0
- package/src/components/LottiePlayer/loadWebLottieView.tsx +64 -0
- package/src/components/PageHero/PageHero.tsx +257 -0
- package/src/components/PoweredByLabel/PoweredByLabel.tsx +221 -0
- package/src/components/PoweredByLabel/finvu.png +0 -0
- package/src/components/RechargeCard/RechargeCard.tsx +32 -24
- package/src/components/Text/Text.tsx +78 -3
- package/src/components/Tooltip/Tooltip.tsx +50 -25
- package/src/components/index.ts +16 -1
- package/src/design-tokens/Coin Variables-variables-full.json +1 -1
- package/src/icons/Icon.tsx +17 -0
- package/src/icons/registry.ts +1 -1
- package/src/index.ts +1 -0
- package/src/skeleton/Skeleton.tsx +298 -0
- package/src/skeleton/SkeletonGroup.tsx +193 -0
- package/src/skeleton/index.ts +10 -0
- package/src/skeleton/shimmer-tokens.ts +221 -0
- package/src/skeleton/useReducedMotion.ts +72 -0
|
@@ -1,223 +1,373 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import React, {
|
|
4
|
-
import { View, Text } from 'react-native';
|
|
3
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
|
4
|
+
import { View, Text, TextInput as RNTextInput } from 'react-native';
|
|
5
5
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
|
-
import { EMPTY_MODES } from '../../utils/react-utils';
|
|
7
6
|
import { useTokens } from '../../design-tokens/JFSThemeProvider';
|
|
8
|
-
import
|
|
7
|
+
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
|
|
9
8
|
import SupportText from '../SupportText/SupportText';
|
|
9
|
+
import Icon from '../../icons/Icon';
|
|
10
|
+
import { useFormContext } from '../Form/Form';
|
|
10
11
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
isDisabled = false,
|
|
15
|
-
isInvalid = false,
|
|
16
|
-
supportText,
|
|
17
|
-
errorMessage,
|
|
18
|
-
modes: propModes = EMPTY_MODES,
|
|
19
|
-
onFocus,
|
|
20
|
-
onBlur
|
|
21
|
-
} = props;
|
|
22
|
-
const {
|
|
23
|
-
modes: globalModes
|
|
24
|
-
} = useTokens();
|
|
25
|
-
const baseModes = useMemo(() => ({
|
|
26
|
-
...globalModes,
|
|
27
|
-
...propModes
|
|
28
|
-
}), [globalModes, propModes]);
|
|
29
|
-
const [isFocused, setIsFocused] = useState(false);
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Token resolution
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
30
15
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const gap = parseInt(getVariableByName('formField/gap', modes), 10) || 8;
|
|
44
|
-
|
|
45
|
-
// -- Input tokens (from "FormField / Output" + "FormField States" collections) --
|
|
46
|
-
const inputPaddingH = parseInt(getVariableByName('formField/input/padding/horizontal', modes), 10) || 12;
|
|
47
|
-
const inputGap = parseInt(getVariableByName('formField/input/gap', modes), 10) || 8;
|
|
48
|
-
const inputRadius = parseInt(getVariableByName('formField/input/radius', modes), 10) || 8;
|
|
49
|
-
const inputBackground = getVariableByName('formField/input/background', modes) || '#ffffff';
|
|
50
|
-
const inputFontSize = parseInt(getVariableByName('formField/input/label/fontSize', modes), 10) || 16;
|
|
51
|
-
const inputLineHeight = parseInt(getVariableByName('formField/input/label/lineHeight', modes), 10) || 45;
|
|
52
|
-
const inputFontFamily = getVariableByName('formField/input/label/fontFamily', modes) || 'JioType Var';
|
|
53
|
-
const inputFontWeight = getVariableByName('formField/input/label/fontWeight', modes) || '400';
|
|
54
|
-
const inputTextColor = getVariableByName('states/formField/input/label/color', modes) || getVariableByName('formField/input/label/color', modes) || '#24262b';
|
|
55
|
-
const inputBorderColor = getVariableByName('states/formField/input/border/color', modes) || getVariableByName('formField/input/border/color', modes) || '#b5b6b7';
|
|
56
|
-
const inputBorderSize = parseInt(getVariableByName('formField/input/border/size', modes), 10) || 1;
|
|
57
|
-
|
|
58
|
-
// -- Styles --
|
|
59
|
-
const labelStyle = useMemo(() => ({
|
|
60
|
-
color: labelColor,
|
|
61
|
-
fontFamily: labelFontFamily,
|
|
62
|
-
fontSize: labelFontSize,
|
|
63
|
-
lineHeight: labelLineHeight,
|
|
64
|
-
fontWeight: labelFontWeight
|
|
65
|
-
}), [labelColor, labelFontFamily, labelFontSize, labelLineHeight, labelFontWeight]);
|
|
66
|
-
const wrapperStyle = useMemo(() => ({
|
|
67
|
-
gap,
|
|
68
|
-
opacity: isDisabled ? 0.5 : 1
|
|
69
|
-
}), [gap, isDisabled]);
|
|
70
|
-
const requiredIndicatorStyle = useMemo(() => ({
|
|
71
|
-
color: '#d93d3d',
|
|
72
|
-
fontFamily: labelFontFamily,
|
|
73
|
-
fontSize: labelFontSize,
|
|
74
|
-
lineHeight: labelLineHeight,
|
|
75
|
-
fontWeight: labelFontWeight
|
|
76
|
-
}), [labelFontFamily, labelFontSize, labelLineHeight, labelFontWeight]);
|
|
77
|
-
|
|
78
|
-
// Style overrides for the input row, sourced from formField/input/* tokens
|
|
79
|
-
const inputContainerStyle = useMemo(() => ({
|
|
80
|
-
backgroundColor: inputBackground,
|
|
81
|
-
borderColor: inputBorderColor,
|
|
82
|
-
borderWidth: inputBorderSize,
|
|
83
|
-
borderRadius: inputRadius,
|
|
84
|
-
paddingHorizontal: inputPaddingH,
|
|
85
|
-
paddingVertical: 0,
|
|
86
|
-
gap: inputGap
|
|
87
|
-
}), [inputBackground, inputBorderColor, inputBorderSize, inputRadius, inputPaddingH, inputGap]);
|
|
88
|
-
const inputTextStyle = useMemo(() => ({
|
|
89
|
-
color: inputTextColor,
|
|
90
|
-
fontSize: inputFontSize,
|
|
91
|
-
lineHeight: inputLineHeight,
|
|
92
|
-
fontFamily: inputFontFamily,
|
|
93
|
-
fontWeight: inputFontWeight
|
|
94
|
-
}), [inputTextColor, inputFontSize, inputLineHeight, inputFontFamily, inputFontWeight]);
|
|
95
|
-
|
|
96
|
-
// -- Support text logic --
|
|
97
|
-
const supportStatus = isInvalid ? 'Error' : 'Neutral';
|
|
98
|
-
const supportLabel = isInvalid && errorMessage ? errorMessage : supportText;
|
|
99
|
-
|
|
100
|
-
// -- Input type derived props --
|
|
101
|
-
const secureTextEntry = type === 'password';
|
|
102
|
-
const keyboardType = type === 'email' ? 'email-address' : 'default';
|
|
103
|
-
const autoCapitalize = type === 'email' || type === 'password' ? 'none' : 'sentences';
|
|
104
|
-
|
|
105
|
-
// -- Event handlers --
|
|
106
|
-
const handleFocus = useCallback(e => {
|
|
107
|
-
setIsFocused(true);
|
|
108
|
-
onFocus?.(e);
|
|
109
|
-
}, [onFocus]);
|
|
110
|
-
const handleBlur = useCallback(e => {
|
|
111
|
-
setIsFocused(false);
|
|
112
|
-
onBlur?.(e);
|
|
113
|
-
}, [onBlur]);
|
|
114
|
-
return {
|
|
115
|
-
modes,
|
|
116
|
-
labelStyle,
|
|
117
|
-
wrapperStyle,
|
|
118
|
-
requiredIndicatorStyle,
|
|
119
|
-
inputContainerStyle,
|
|
120
|
-
inputTextStyle,
|
|
121
|
-
supportStatus,
|
|
122
|
-
supportLabel,
|
|
123
|
-
secureTextEntry,
|
|
124
|
-
keyboardType,
|
|
125
|
-
autoCapitalize,
|
|
126
|
-
handleFocus,
|
|
127
|
-
handleBlur
|
|
128
|
-
};
|
|
16
|
+
function toNumber(value, fallback) {
|
|
17
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
18
|
+
if (typeof value === 'string') {
|
|
19
|
+
const parsed = parseFloat(value);
|
|
20
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
21
|
+
}
|
|
22
|
+
return fallback;
|
|
23
|
+
}
|
|
24
|
+
function toFontWeight(value, fallback) {
|
|
25
|
+
if (typeof value === 'number') return value.toString();
|
|
26
|
+
if (typeof value === 'string' && value.length > 0) return value;
|
|
27
|
+
return fallback;
|
|
129
28
|
}
|
|
29
|
+
function useFormFieldTokens(modes) {
|
|
30
|
+
return useMemo(() => {
|
|
31
|
+
// Wrapper
|
|
32
|
+
const gap = toNumber(getVariableByName('formField/gap', modes), 8);
|
|
33
|
+
|
|
34
|
+
// Label (Figma: 14/17 medium, color #0c0d10)
|
|
35
|
+
const labelColor = getVariableByName('formField/label/color', modes) || '#0c0d10';
|
|
36
|
+
const labelFontFamily = getVariableByName('formField/label/fontFamily', modes) || 'JioType Var';
|
|
37
|
+
const labelFontSize = toNumber(getVariableByName('formField/label/fontSize', modes), 14);
|
|
38
|
+
const labelLineHeight = toNumber(getVariableByName('formField/label/lineHeight', modes), 17);
|
|
39
|
+
const labelFontWeight = toFontWeight(getVariableByName('formField/label/fontWeight', modes), '500');
|
|
40
|
+
|
|
41
|
+
// Input row (Figma: 12 px padding-h, 8 px gap, 8 px radius, 1 px border)
|
|
42
|
+
const inputPaddingH = toNumber(getVariableByName('formField/input/padding/horizontal', modes), 12);
|
|
43
|
+
const inputGap = toNumber(getVariableByName('formField/input/gap', modes), 8);
|
|
44
|
+
const inputRadius = toNumber(getVariableByName('formField/input/radius', modes), 8);
|
|
45
|
+
const inputBorderSize = toNumber(getVariableByName('formField/input/border/size', modes), 1);
|
|
46
|
+
|
|
47
|
+
// Input text (Figma: 16/45 regular)
|
|
48
|
+
const inputFontSize = toNumber(getVariableByName('formField/input/label/fontSize', modes), 16);
|
|
49
|
+
const inputLineHeight = toNumber(getVariableByName('formField/input/label/lineHeight', modes), 45);
|
|
50
|
+
const inputFontFamily = getVariableByName('formField/input/label/fontFamily', modes) || 'JioType Var';
|
|
51
|
+
const inputFontWeight = toFontWeight(getVariableByName('formField/input/label/fontWeight', modes), '400');
|
|
52
|
+
const inputBackground = getVariableByName('formField/input/background', modes) || '#ffffff';
|
|
53
|
+
const inputBorderColor = getVariableByName('formField/input/border/color', modes) || '#b5b6b7';
|
|
54
|
+
if (__DEV__) {
|
|
55
|
+
console.warn('[FormField] border color (modes changed)', {
|
|
56
|
+
'FormField States': modes['FormField States'],
|
|
57
|
+
inputBorderColor,
|
|
58
|
+
'formField/input/border/color': getVariableByName('formField/input/border/color', modes)
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const inputTextColor = getVariableByName('formField/input/label/color', modes) || '#24262b';
|
|
62
|
+
return {
|
|
63
|
+
gap,
|
|
64
|
+
labelColor,
|
|
65
|
+
labelFontFamily,
|
|
66
|
+
labelFontSize,
|
|
67
|
+
labelLineHeight,
|
|
68
|
+
labelFontWeight,
|
|
69
|
+
inputPaddingH,
|
|
70
|
+
inputGap,
|
|
71
|
+
inputRadius,
|
|
72
|
+
inputBorderSize,
|
|
73
|
+
inputFontSize,
|
|
74
|
+
inputLineHeight,
|
|
75
|
+
inputFontFamily,
|
|
76
|
+
inputFontWeight,
|
|
77
|
+
inputBackground,
|
|
78
|
+
inputBorderColor,
|
|
79
|
+
inputTextColor
|
|
80
|
+
};
|
|
81
|
+
}, [modes]);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Helpers
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
function deriveTypeProps(type) {
|
|
89
|
+
switch (type) {
|
|
90
|
+
case 'password':
|
|
91
|
+
return {
|
|
92
|
+
secureTextEntry: true,
|
|
93
|
+
keyboardType: 'default',
|
|
94
|
+
autoCapitalize: 'none',
|
|
95
|
+
autoComplete: 'password',
|
|
96
|
+
textContentType: 'password'
|
|
97
|
+
};
|
|
98
|
+
case 'email':
|
|
99
|
+
return {
|
|
100
|
+
secureTextEntry: false,
|
|
101
|
+
keyboardType: 'email-address',
|
|
102
|
+
autoCapitalize: 'none',
|
|
103
|
+
autoComplete: 'email',
|
|
104
|
+
textContentType: 'emailAddress'
|
|
105
|
+
};
|
|
106
|
+
case 'number':
|
|
107
|
+
return {
|
|
108
|
+
secureTextEntry: false,
|
|
109
|
+
keyboardType: 'numeric',
|
|
110
|
+
autoCapitalize: 'none',
|
|
111
|
+
autoComplete: 'off',
|
|
112
|
+
textContentType: 'none'
|
|
113
|
+
};
|
|
114
|
+
case 'phone':
|
|
115
|
+
return {
|
|
116
|
+
secureTextEntry: false,
|
|
117
|
+
keyboardType: 'phone-pad',
|
|
118
|
+
autoCapitalize: 'none',
|
|
119
|
+
autoComplete: 'tel',
|
|
120
|
+
textContentType: 'telephoneNumber'
|
|
121
|
+
};
|
|
122
|
+
case 'url':
|
|
123
|
+
return {
|
|
124
|
+
secureTextEntry: false,
|
|
125
|
+
keyboardType: 'url',
|
|
126
|
+
autoCapitalize: 'none',
|
|
127
|
+
autoComplete: 'url',
|
|
128
|
+
textContentType: 'URL'
|
|
129
|
+
};
|
|
130
|
+
case 'search':
|
|
131
|
+
return {
|
|
132
|
+
secureTextEntry: false,
|
|
133
|
+
keyboardType: 'default',
|
|
134
|
+
autoCapitalize: 'none',
|
|
135
|
+
autoComplete: 'off',
|
|
136
|
+
textContentType: 'none'
|
|
137
|
+
};
|
|
138
|
+
case 'text':
|
|
139
|
+
default:
|
|
140
|
+
return {
|
|
141
|
+
secureTextEntry: false,
|
|
142
|
+
keyboardType: 'default',
|
|
143
|
+
autoCapitalize: 'sentences',
|
|
144
|
+
autoComplete: 'off',
|
|
145
|
+
textContentType: 'none'
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function firstError(error) {
|
|
150
|
+
if (!error) return undefined;
|
|
151
|
+
if (Array.isArray(error)) return error[0];
|
|
152
|
+
return error;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
// Component
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
|
|
130
159
|
function FormField({
|
|
131
160
|
label,
|
|
132
161
|
placeholder,
|
|
133
|
-
value
|
|
162
|
+
value,
|
|
134
163
|
onChangeText,
|
|
135
164
|
type = 'text',
|
|
165
|
+
name,
|
|
136
166
|
leading,
|
|
137
167
|
trailing,
|
|
138
168
|
leadingIconName,
|
|
139
169
|
isRequired = false,
|
|
140
170
|
isDisabled = false,
|
|
141
171
|
isInvalid = false,
|
|
172
|
+
isReadOnly = false,
|
|
142
173
|
supportText,
|
|
143
174
|
errorMessage,
|
|
144
|
-
|
|
175
|
+
maxLength,
|
|
176
|
+
autoFocus = false,
|
|
177
|
+
modes: propModes = EMPTY_MODES,
|
|
145
178
|
style,
|
|
179
|
+
inputStyle,
|
|
180
|
+
inputTextStyle,
|
|
146
181
|
onFocus,
|
|
147
182
|
onBlur,
|
|
183
|
+
onSubmitEditing,
|
|
148
184
|
accessibilityLabel,
|
|
149
|
-
accessibilityHint
|
|
185
|
+
accessibilityHint,
|
|
186
|
+
testID
|
|
150
187
|
}) {
|
|
188
|
+
// -- Form context integration -------------------------------------------
|
|
189
|
+
const formCtx = useFormContext();
|
|
190
|
+
const formError = name && formCtx ? firstError(formCtx.validationErrors[name]) : undefined;
|
|
191
|
+
const resolvedIsInvalid = isInvalid || Boolean(formError);
|
|
192
|
+
const resolvedErrorMessage = errorMessage ?? formError;
|
|
193
|
+
|
|
194
|
+
// -- Mode resolution ----------------------------------------------------
|
|
151
195
|
const {
|
|
152
|
-
modes:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
196
|
+
modes: globalModes
|
|
197
|
+
} = useTokens();
|
|
198
|
+
const baseModes = useMemo(() => ({
|
|
199
|
+
...globalModes,
|
|
200
|
+
...propModes
|
|
201
|
+
}), [globalModes, propModes]);
|
|
202
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
203
|
+
const interactive = !isDisabled && !isReadOnly;
|
|
204
|
+
|
|
205
|
+
// FormField States cascade — error > read only/disabled > active (focused) > idle.
|
|
206
|
+
// Disabled maps to "Read Only" since there is no dedicated disabled mode and
|
|
207
|
+
// the visual treatment is closest. This is only the DEFAULT — an explicit
|
|
208
|
+
// `modes['FormField States']` passed in via props or the global theme
|
|
209
|
+
// always wins so consumers can force a state (e.g. for documentation).
|
|
210
|
+
const derivedStateMode = useMemo(() => {
|
|
211
|
+
if (resolvedIsInvalid) return 'Error';
|
|
212
|
+
if (isReadOnly || isDisabled) return 'Read Only';
|
|
213
|
+
if (isFocused) return 'Active';
|
|
214
|
+
return 'Idle';
|
|
215
|
+
}, [resolvedIsInvalid, isReadOnly, isDisabled, isFocused]);
|
|
216
|
+
const modes = useMemo(() => {
|
|
217
|
+
const explicitStateMode = baseModes['FormField States'];
|
|
218
|
+
const stateMode = explicitStateMode ?? derivedStateMode;
|
|
219
|
+
const explicitStatus = baseModes.Status;
|
|
220
|
+
// Default SupportText token mode is Auto (Figma resolves foreground from
|
|
221
|
+
// context). Pass modes={{ Status: 'Error' }} etc. to override.
|
|
222
|
+
const status = explicitStatus ?? 'Auto';
|
|
223
|
+
return {
|
|
224
|
+
...baseModes,
|
|
225
|
+
'FormField States': stateMode,
|
|
226
|
+
Status: status
|
|
227
|
+
};
|
|
228
|
+
}, [baseModes, derivedStateMode]);
|
|
229
|
+
const tokens = useFormFieldTokens(modes);
|
|
230
|
+
|
|
231
|
+
// -- Type-derived input props ------------------------------------------
|
|
232
|
+
const typeProps = useMemo(() => deriveTypeProps(type), [type]);
|
|
233
|
+
|
|
234
|
+
// -- Event handlers ----------------------------------------------------
|
|
235
|
+
const handleFocus = useCallback(e => {
|
|
236
|
+
setIsFocused(true);
|
|
237
|
+
onFocus?.(e);
|
|
238
|
+
}, [onFocus]);
|
|
239
|
+
const handleBlur = useCallback(e => {
|
|
240
|
+
setIsFocused(false);
|
|
241
|
+
onBlur?.(e);
|
|
242
|
+
}, [onBlur]);
|
|
243
|
+
const handleChangeText = useCallback(next => {
|
|
244
|
+
onChangeText?.(next);
|
|
245
|
+
if (name && formCtx) formCtx.onFieldChange(name);
|
|
246
|
+
}, [onChangeText, name, formCtx]);
|
|
247
|
+
|
|
248
|
+
// -- Styles ------------------------------------------------------------
|
|
249
|
+
const wrapperStyle = useMemo(() => ({
|
|
250
|
+
gap: tokens.gap,
|
|
251
|
+
opacity: isDisabled ? 0.5 : 1
|
|
252
|
+
}), [tokens.gap, isDisabled]);
|
|
253
|
+
const labelRowStyle = useMemo(() => ({
|
|
254
|
+
flexDirection: 'row',
|
|
255
|
+
alignItems: 'baseline'
|
|
256
|
+
}), []);
|
|
257
|
+
const labelTextStyle = useMemo(() => ({
|
|
258
|
+
color: tokens.labelColor,
|
|
259
|
+
fontFamily: tokens.labelFontFamily,
|
|
260
|
+
fontSize: tokens.labelFontSize,
|
|
261
|
+
lineHeight: tokens.labelLineHeight,
|
|
262
|
+
fontWeight: tokens.labelFontWeight
|
|
263
|
+
}), [tokens.labelColor, tokens.labelFontFamily, tokens.labelFontSize, tokens.labelLineHeight, tokens.labelFontWeight]);
|
|
264
|
+
const requiredIndicatorStyle = useMemo(() => ({
|
|
265
|
+
...labelTextStyle,
|
|
266
|
+
color: '#d93d3d'
|
|
267
|
+
}), [labelTextStyle]);
|
|
268
|
+
const inputRowStyle = useMemo(() => ({
|
|
269
|
+
flexDirection: 'row',
|
|
270
|
+
alignItems: 'center',
|
|
271
|
+
backgroundColor: tokens.inputBackground,
|
|
272
|
+
borderColor: tokens.inputBorderColor,
|
|
273
|
+
borderWidth: tokens.inputBorderSize,
|
|
274
|
+
borderStyle: 'solid',
|
|
275
|
+
borderRadius: tokens.inputRadius,
|
|
276
|
+
paddingHorizontal: tokens.inputPaddingH,
|
|
277
|
+
paddingVertical: 0,
|
|
278
|
+
gap: tokens.inputGap,
|
|
279
|
+
minHeight: tokens.inputLineHeight,
|
|
280
|
+
width: '100%'
|
|
281
|
+
}), [tokens.inputBackground, tokens.inputBorderColor, tokens.inputBorderSize, tokens.inputRadius, tokens.inputPaddingH, tokens.inputGap, tokens.inputLineHeight]);
|
|
282
|
+
const inputTextStyles = useMemo(() => ({
|
|
283
|
+
flex: 1,
|
|
284
|
+
color: tokens.inputTextColor,
|
|
285
|
+
fontFamily: tokens.inputFontFamily,
|
|
286
|
+
fontSize: tokens.inputFontSize,
|
|
287
|
+
lineHeight: tokens.inputLineHeight,
|
|
288
|
+
fontWeight: tokens.inputFontWeight,
|
|
289
|
+
padding: 0,
|
|
290
|
+
margin: 0,
|
|
291
|
+
// Remove the default web focus ring; the input row's border acts as the
|
|
292
|
+
// focus indicator via the FormField States cascade.
|
|
293
|
+
outlineStyle: 'none',
|
|
294
|
+
outlineWidth: 0,
|
|
295
|
+
outlineColor: 'transparent'
|
|
296
|
+
}), [tokens.inputTextColor, tokens.inputFontFamily, tokens.inputFontSize, tokens.inputLineHeight, tokens.inputFontWeight]);
|
|
297
|
+
const placeholderColor = useMemo(() => {
|
|
298
|
+
// Slightly muted version of the resolved text color, mirroring the
|
|
299
|
+
// sibling TextInput behavior.
|
|
300
|
+
const c = tokens.inputTextColor;
|
|
301
|
+
if (typeof c !== 'string') return undefined;
|
|
302
|
+
if (c.startsWith('rgb(')) {
|
|
303
|
+
return c.replace('rgb(', 'rgba(').replace(')', ', 0.55)');
|
|
304
|
+
}
|
|
305
|
+
return '#888a8d';
|
|
306
|
+
}, [tokens.inputTextColor]);
|
|
307
|
+
|
|
308
|
+
// -- Slots --------------------------------------------------------------
|
|
309
|
+
const leadingElement = leading ?? (leadingIconName ? /*#__PURE__*/_jsx(Icon, {
|
|
310
|
+
name: leadingIconName,
|
|
311
|
+
size: 20,
|
|
312
|
+
color: tokens.inputTextColor
|
|
313
|
+
}) : null);
|
|
314
|
+
const processedLeading = leadingElement ? cloneChildrenWithModes(leadingElement, modes) : null;
|
|
315
|
+
const processedTrailing = trailing ? cloneChildrenWithModes(trailing, modes) : null;
|
|
316
|
+
|
|
317
|
+
// -- Support text -------------------------------------------------------
|
|
318
|
+
const supportStatus = resolvedIsInvalid ? 'Error' : 'Neutral';
|
|
319
|
+
const supportLabel = resolvedIsInvalid && resolvedErrorMessage ? resolvedErrorMessage : supportText;
|
|
320
|
+
|
|
321
|
+
// -- Accessibility ------------------------------------------------------
|
|
175
322
|
const resolvedA11yLabel = accessibilityLabel || label || placeholder || 'Form field';
|
|
176
323
|
return /*#__PURE__*/_jsxs(View, {
|
|
177
324
|
style: [wrapperStyle, style],
|
|
178
325
|
pointerEvents: isDisabled ? 'none' : 'auto',
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
accessibilityLabel: resolvedA11yLabel,
|
|
182
|
-
accessibilityState: {
|
|
183
|
-
disabled: isDisabled
|
|
184
|
-
},
|
|
326
|
+
testID: testID,
|
|
327
|
+
accessible: false,
|
|
185
328
|
children: [label != null && /*#__PURE__*/_jsxs(View, {
|
|
186
|
-
style:
|
|
187
|
-
flexDirection: 'row',
|
|
188
|
-
alignItems: 'baseline'
|
|
189
|
-
},
|
|
329
|
+
style: labelRowStyle,
|
|
190
330
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
191
|
-
style:
|
|
331
|
+
style: labelTextStyle,
|
|
192
332
|
children: label
|
|
193
333
|
}), isRequired && /*#__PURE__*/_jsx(Text, {
|
|
194
334
|
style: requiredIndicatorStyle,
|
|
195
335
|
children: " *"
|
|
196
336
|
})]
|
|
197
|
-
}), /*#__PURE__*/
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
337
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
338
|
+
style: [inputRowStyle, inputStyle],
|
|
339
|
+
children: [processedLeading != null && /*#__PURE__*/_jsx(View, {
|
|
340
|
+
accessibilityElementsHidden: true,
|
|
341
|
+
importantForAccessibility: "no",
|
|
342
|
+
children: processedLeading
|
|
343
|
+
}), /*#__PURE__*/_jsx(RNTextInput, {
|
|
344
|
+
style: [inputTextStyles, inputTextStyle],
|
|
345
|
+
value: value ?? '',
|
|
346
|
+
onChangeText: handleChangeText,
|
|
347
|
+
onFocus: handleFocus,
|
|
348
|
+
onBlur: handleBlur,
|
|
349
|
+
onSubmitEditing: onSubmitEditing,
|
|
350
|
+
placeholder: placeholder ?? '',
|
|
351
|
+
placeholderTextColor: placeholderColor,
|
|
352
|
+
editable: interactive,
|
|
353
|
+
maxLength: maxLength,
|
|
354
|
+
autoFocus: autoFocus,
|
|
355
|
+
secureTextEntry: typeProps.secureTextEntry,
|
|
356
|
+
keyboardType: typeProps.keyboardType,
|
|
357
|
+
autoCapitalize: typeProps.autoCapitalize,
|
|
358
|
+
autoComplete: typeProps.autoComplete,
|
|
359
|
+
textContentType: typeProps.textContentType,
|
|
360
|
+
accessibilityLabel: resolvedA11yLabel,
|
|
361
|
+
accessibilityHint: accessibilityHint
|
|
362
|
+
}), processedTrailing != null && /*#__PURE__*/_jsx(View, {
|
|
363
|
+
accessibilityElementsHidden: true,
|
|
364
|
+
importantForAccessibility: "no",
|
|
365
|
+
children: processedTrailing
|
|
366
|
+
})]
|
|
367
|
+
}), supportLabel != null && supportLabel !== '' && /*#__PURE__*/_jsx(SupportText, {
|
|
218
368
|
label: supportLabel,
|
|
219
369
|
status: supportStatus,
|
|
220
|
-
modes:
|
|
370
|
+
modes: modes
|
|
221
371
|
})]
|
|
222
372
|
});
|
|
223
373
|
}
|
|
@@ -6,6 +6,8 @@ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
|
6
6
|
import Icon from '../../icons/Icon';
|
|
7
7
|
import { usePressableWebSupport } from '../../utils/web-platform-utils';
|
|
8
8
|
import { EMPTY_MODES } from '../../utils/react-utils';
|
|
9
|
+
import Skeleton from '../../skeleton/Skeleton';
|
|
10
|
+
import { useSkeleton } from '../../skeleton/SkeletonGroup';
|
|
9
11
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
10
12
|
// ---------------------------------------------------------------------------
|
|
11
13
|
// Module-scope constants
|
|
@@ -93,6 +95,7 @@ function IconButton({
|
|
|
93
95
|
inactiveIcon,
|
|
94
96
|
inactiveSource,
|
|
95
97
|
isActive = false,
|
|
98
|
+
loading,
|
|
96
99
|
...rest
|
|
97
100
|
}) {
|
|
98
101
|
// Merge explicit props with modes for token resolution. Memoize the merged
|
|
@@ -104,6 +107,13 @@ function IconButton({
|
|
|
104
107
|
isActive
|
|
105
108
|
}), [modes, isToggle, isActive]);
|
|
106
109
|
const tokens = useMemo(() => resolveIconButtonTokens(componentModes, disabled), [componentModes, disabled]);
|
|
110
|
+
|
|
111
|
+
// Hook called unconditionally — short-circuit below comes AFTER all hooks
|
|
112
|
+
// to keep React's hook order stable across renders.
|
|
113
|
+
const {
|
|
114
|
+
active: groupActive
|
|
115
|
+
} = useSkeleton();
|
|
116
|
+
const isLoading = loading ?? groupActive;
|
|
107
117
|
const [isFocused, setIsFocused] = useState(false);
|
|
108
118
|
const [isHovered, setIsHovered] = useState(false);
|
|
109
119
|
const userHandlersRef = useRef({});
|
|
@@ -175,6 +185,16 @@ function IconButton({
|
|
|
175
185
|
const styleCallback = useCallback(({
|
|
176
186
|
pressed
|
|
177
187
|
}) => [tokens.baseContainerStyle, style, pressed && !disabled ? pressedOverlayStyle : null, isHovered && !disabled ? hoverOverlayStyle : null, isFocused && !disabled ? focusOverlayStyle : null], [tokens.baseContainerStyle, style, isHovered, isFocused, disabled]);
|
|
188
|
+
if (isLoading) {
|
|
189
|
+
const size = tokens.baseContainerStyle.width;
|
|
190
|
+
return /*#__PURE__*/_jsx(Skeleton, {
|
|
191
|
+
kind: "other",
|
|
192
|
+
width: size,
|
|
193
|
+
height: size,
|
|
194
|
+
style: style,
|
|
195
|
+
modes: componentModes
|
|
196
|
+
});
|
|
197
|
+
}
|
|
178
198
|
return /*#__PURE__*/_jsx(Pressable, {
|
|
179
199
|
accessibilityRole: "button",
|
|
180
200
|
accessibilityLabel: undefined,
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { useMemo } from 'react';
|
|
4
4
|
import { Image as RNImage, View } from 'react-native';
|
|
5
|
+
import Skeleton from '../../skeleton/Skeleton';
|
|
6
|
+
import { useSkeleton } from '../../skeleton/SkeletonGroup';
|
|
5
7
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
6
8
|
function normalizeSource(imageSource) {
|
|
7
9
|
if (imageSource == null) return undefined;
|
|
@@ -41,7 +43,8 @@ function Image({
|
|
|
41
43
|
style,
|
|
42
44
|
accessibilityLabel,
|
|
43
45
|
accessibilityElementsHidden,
|
|
44
|
-
importantForAccessibility
|
|
46
|
+
importantForAccessibility,
|
|
47
|
+
loading
|
|
45
48
|
}) {
|
|
46
49
|
const source = useMemo(() => normalizeSource(imageSource), [imageSource]);
|
|
47
50
|
const layoutStyle = useMemo(() => {
|
|
@@ -63,6 +66,27 @@ function Image({
|
|
|
63
66
|
if (borderRadius != null) s.borderRadius = borderRadius;
|
|
64
67
|
return s;
|
|
65
68
|
}, [ratio, width, height, borderRadius]);
|
|
69
|
+
const {
|
|
70
|
+
active: groupActive
|
|
71
|
+
} = useSkeleton();
|
|
72
|
+
const isLoading = loading ?? groupActive;
|
|
73
|
+
if (isLoading) {
|
|
74
|
+
// Match the loaded image's exact box. If height is unknown but a ratio
|
|
75
|
+
// is set, the skeleton uses `aspectRatio` the same way the loaded image
|
|
76
|
+
// would, so layout never jumps when the load resolves.
|
|
77
|
+
const skeletonStyle = {
|
|
78
|
+
width: width ?? '100%',
|
|
79
|
+
...(height != null ? {
|
|
80
|
+
height: height
|
|
81
|
+
} : {
|
|
82
|
+
aspectRatio: ratio
|
|
83
|
+
})
|
|
84
|
+
};
|
|
85
|
+
return /*#__PURE__*/_jsx(Skeleton, {
|
|
86
|
+
kind: "image",
|
|
87
|
+
style: skeletonStyle
|
|
88
|
+
});
|
|
89
|
+
}
|
|
66
90
|
if (!source) {
|
|
67
91
|
return /*#__PURE__*/_jsx(View, {
|
|
68
92
|
style: [layoutStyle, style]
|