jfs-components 0.0.72 → 0.0.74

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.
Files changed (158) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/lib/commonjs/components/AccordionCheckbox/AccordionCheckbox.js +239 -0
  3. package/lib/commonjs/components/AccountCard/AccountCard.js +247 -0
  4. package/lib/commonjs/components/AppBar/AppBar.js +17 -11
  5. package/lib/commonjs/components/BrandChip/BrandChip.js +149 -0
  6. package/lib/commonjs/components/CardBankAccount/CardBankAccount.js +229 -0
  7. package/lib/commonjs/components/CardInsight/CardInsight.js +166 -0
  8. package/lib/commonjs/components/CheckboxGroup/CheckboxGroup.js +67 -0
  9. package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +140 -0
  10. package/lib/commonjs/components/CircularProgressBar/CircularProgressBar.js +56 -9
  11. package/lib/commonjs/components/CoverageBarComparison/CoverageBarComparison.js +272 -0
  12. package/lib/commonjs/components/CoverageRing/CoverageRing.js +141 -0
  13. package/lib/commonjs/components/DonutChart/DonutChart.js +309 -0
  14. package/lib/commonjs/components/DonutChartSummary/DonutChartSummary.js +155 -0
  15. package/lib/commonjs/components/Dropdown/Dropdown.js +214 -0
  16. package/lib/commonjs/components/DropdownInput/DropdownInput.js +542 -0
  17. package/lib/commonjs/components/FormField/FormField.js +328 -178
  18. package/lib/commonjs/components/LinearMeter/LinearMeter.js +9 -28
  19. package/lib/commonjs/components/LinearProgress/LinearProgress.js +68 -0
  20. package/lib/commonjs/components/LottieIntroBlock/LottieIntroBlock.js +150 -0
  21. package/lib/commonjs/components/MetricLegendItem/MetricLegendItem.js +95 -0
  22. package/lib/commonjs/components/MonthlyStatusGrid/MonthlyStatusGrid.js +286 -0
  23. package/lib/commonjs/components/OTP/OTP.js +381 -37
  24. package/lib/commonjs/components/PageHero/PageHero.js +153 -0
  25. package/lib/commonjs/components/PoweredByLabel/PoweredByLabel.js +135 -0
  26. package/lib/commonjs/components/PoweredByLabel/finvu.png +0 -0
  27. package/lib/commonjs/components/ProductOverview/ProductOverview.js +147 -0
  28. package/lib/commonjs/components/RangeTrack/RangeTrack.js +269 -0
  29. package/lib/commonjs/components/SavingsGoalSummary/SavingsGoalSummary.js +181 -0
  30. package/lib/commonjs/components/SegmentedTrack/SegmentedTrack.js +171 -0
  31. package/lib/commonjs/components/StatGroup/StatGroup.js +128 -0
  32. package/lib/commonjs/components/StatItem/StatItem.js +65 -35
  33. package/lib/commonjs/components/StrengthIndicator/StrengthIndicator.js +157 -0
  34. package/lib/commonjs/components/SummaryTile/SummaryTile.js +150 -0
  35. package/lib/commonjs/components/Text/Text.js +9 -2
  36. package/lib/commonjs/components/Tooltip/Tooltip.js +34 -27
  37. package/lib/commonjs/components/index.js +231 -1
  38. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  39. package/lib/commonjs/icons/registry.js +1 -1
  40. package/lib/commonjs/utils/index.js +7 -0
  41. package/lib/commonjs/utils/number-utils.js +57 -0
  42. package/lib/module/components/AccordionCheckbox/AccordionCheckbox.js +233 -0
  43. package/lib/module/components/AccountCard/AccountCard.js +241 -0
  44. package/lib/module/components/AppBar/AppBar.js +17 -11
  45. package/lib/module/components/BrandChip/BrandChip.js +143 -0
  46. package/lib/module/components/CardBankAccount/CardBankAccount.js +223 -0
  47. package/lib/module/components/CardInsight/CardInsight.js +161 -0
  48. package/lib/module/components/CheckboxGroup/CheckboxGroup.js +62 -0
  49. package/lib/module/components/CheckboxItem/CheckboxItem.js +134 -0
  50. package/lib/module/components/CircularProgressBar/CircularProgressBar.js +56 -9
  51. package/lib/module/components/CoverageBarComparison/CoverageBarComparison.js +266 -0
  52. package/lib/module/components/CoverageRing/CoverageRing.js +136 -0
  53. package/lib/module/components/DonutChart/DonutChart.js +303 -0
  54. package/lib/module/components/DonutChartSummary/DonutChartSummary.js +150 -0
  55. package/lib/module/components/Dropdown/Dropdown.js +206 -0
  56. package/lib/module/components/DropdownInput/DropdownInput.js +536 -0
  57. package/lib/module/components/FormField/FormField.js +330 -180
  58. package/lib/module/components/LinearMeter/LinearMeter.js +9 -28
  59. package/lib/module/components/LinearProgress/LinearProgress.js +63 -0
  60. package/lib/module/components/LottieIntroBlock/LottieIntroBlock.js +144 -0
  61. package/lib/module/components/MetricLegendItem/MetricLegendItem.js +90 -0
  62. package/lib/module/components/MonthlyStatusGrid/MonthlyStatusGrid.js +281 -0
  63. package/lib/module/components/OTP/OTP.js +381 -38
  64. package/lib/module/components/PageHero/PageHero.js +147 -0
  65. package/lib/module/components/PoweredByLabel/PoweredByLabel.js +130 -0
  66. package/lib/module/components/PoweredByLabel/finvu.png +0 -0
  67. package/lib/module/components/ProductOverview/ProductOverview.js +142 -0
  68. package/lib/module/components/RangeTrack/RangeTrack.js +263 -0
  69. package/lib/module/components/SavingsGoalSummary/SavingsGoalSummary.js +175 -0
  70. package/lib/module/components/SegmentedTrack/SegmentedTrack.js +166 -0
  71. package/lib/module/components/StatGroup/StatGroup.js +123 -0
  72. package/lib/module/components/StatItem/StatItem.js +66 -36
  73. package/lib/module/components/StrengthIndicator/StrengthIndicator.js +152 -0
  74. package/lib/module/components/SummaryTile/SummaryTile.js +145 -0
  75. package/lib/module/components/Text/Text.js +9 -2
  76. package/lib/module/components/Tooltip/Tooltip.js +34 -27
  77. package/lib/module/components/index.js +28 -2
  78. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  79. package/lib/module/icons/registry.js +1 -1
  80. package/lib/module/utils/index.js +2 -1
  81. package/lib/module/utils/number-utils.js +53 -0
  82. package/lib/typescript/src/components/AccordionCheckbox/AccordionCheckbox.d.ts +71 -0
  83. package/lib/typescript/src/components/AccountCard/AccountCard.d.ts +81 -0
  84. package/lib/typescript/src/components/BrandChip/BrandChip.d.ts +43 -0
  85. package/lib/typescript/src/components/CardBankAccount/CardBankAccount.d.ts +86 -0
  86. package/lib/typescript/src/components/CardInsight/CardInsight.d.ts +48 -0
  87. package/lib/typescript/src/components/CheckboxGroup/CheckboxGroup.d.ts +41 -0
  88. package/lib/typescript/src/components/CheckboxItem/CheckboxItem.d.ts +72 -0
  89. package/lib/typescript/src/components/CircularProgressBar/CircularProgressBar.d.ts +11 -1
  90. package/lib/typescript/src/components/CoverageBarComparison/CoverageBarComparison.d.ts +105 -0
  91. package/lib/typescript/src/components/CoverageRing/CoverageRing.d.ts +90 -0
  92. package/lib/typescript/src/components/DonutChart/DonutChart.d.ts +117 -0
  93. package/lib/typescript/src/components/DonutChartSummary/DonutChartSummary.d.ts +103 -0
  94. package/lib/typescript/src/components/Dropdown/Dropdown.d.ts +62 -0
  95. package/lib/typescript/src/components/DropdownInput/DropdownInput.d.ts +107 -0
  96. package/lib/typescript/src/components/FormField/FormField.d.ts +76 -19
  97. package/lib/typescript/src/components/LinearProgress/LinearProgress.d.ts +17 -0
  98. package/lib/typescript/src/components/LottieIntroBlock/LottieIntroBlock.d.ts +58 -0
  99. package/lib/typescript/src/components/MetricLegendItem/MetricLegendItem.d.ts +37 -0
  100. package/lib/typescript/src/components/MonthlyStatusGrid/MonthlyStatusGrid.d.ts +119 -0
  101. package/lib/typescript/src/components/OTP/OTP.d.ts +88 -2
  102. package/lib/typescript/src/components/PageHero/PageHero.d.ts +53 -0
  103. package/lib/typescript/src/components/PoweredByLabel/PoweredByLabel.d.ts +70 -0
  104. package/lib/typescript/src/components/ProductOverview/ProductOverview.d.ts +39 -0
  105. package/lib/typescript/src/components/RangeTrack/RangeTrack.d.ts +173 -0
  106. package/lib/typescript/src/components/SavingsGoalSummary/SavingsGoalSummary.d.ts +95 -0
  107. package/lib/typescript/src/components/SegmentedTrack/SegmentedTrack.d.ts +108 -0
  108. package/lib/typescript/src/components/StatGroup/StatGroup.d.ts +45 -0
  109. package/lib/typescript/src/components/StatItem/StatItem.d.ts +24 -7
  110. package/lib/typescript/src/components/StrengthIndicator/StrengthIndicator.d.ts +58 -0
  111. package/lib/typescript/src/components/SummaryTile/SummaryTile.d.ts +60 -0
  112. package/lib/typescript/src/components/Text/Text.d.ts +12 -2
  113. package/lib/typescript/src/components/Tooltip/Tooltip.d.ts +13 -2
  114. package/lib/typescript/src/components/index.d.ts +29 -3
  115. package/lib/typescript/src/icons/registry.d.ts +1 -1
  116. package/lib/typescript/src/utils/index.d.ts +1 -0
  117. package/lib/typescript/src/utils/number-utils.d.ts +29 -0
  118. package/package.json +1 -3
  119. package/src/components/AccordionCheckbox/AccordionCheckbox.tsx +323 -0
  120. package/src/components/AccountCard/AccountCard.tsx +376 -0
  121. package/src/components/AppBar/AppBar.tsx +25 -14
  122. package/src/components/BrandChip/BrandChip.tsx +235 -0
  123. package/src/components/CardBankAccount/CardBankAccount.tsx +321 -0
  124. package/src/components/CardInsight/CardInsight.tsx +239 -0
  125. package/src/components/CheckboxGroup/CheckboxGroup.tsx +86 -0
  126. package/src/components/CheckboxItem/CheckboxItem.tsx +209 -0
  127. package/src/components/CircularProgressBar/CircularProgressBar.tsx +74 -9
  128. package/src/components/CoverageBarComparison/CoverageBarComparison.tsx +378 -0
  129. package/src/components/CoverageRing/CoverageRing.tsx +225 -0
  130. package/src/components/DonutChart/DonutChart.tsx +503 -0
  131. package/src/components/DonutChartSummary/DonutChartSummary.tsx +256 -0
  132. package/src/components/Dropdown/Dropdown.tsx +331 -0
  133. package/src/components/DropdownInput/DropdownInput.tsx +819 -0
  134. package/src/components/FormField/FormField.tsx +542 -215
  135. package/src/components/LinearMeter/LinearMeter.tsx +9 -39
  136. package/src/components/LinearProgress/LinearProgress.tsx +92 -0
  137. package/src/components/LottieIntroBlock/LottieIntroBlock.tsx +202 -0
  138. package/src/components/MetricLegendItem/MetricLegendItem.tsx +167 -0
  139. package/src/components/MonthlyStatusGrid/MonthlyStatusGrid.tsx +438 -0
  140. package/src/components/OTP/OTP.tsx +476 -29
  141. package/src/components/PageHero/PageHero.tsx +200 -0
  142. package/src/components/PoweredByLabel/PoweredByLabel.tsx +221 -0
  143. package/src/components/PoweredByLabel/finvu.png +0 -0
  144. package/src/components/ProductOverview/ProductOverview.tsx +236 -0
  145. package/src/components/RangeTrack/RangeTrack.tsx +394 -0
  146. package/src/components/SavingsGoalSummary/SavingsGoalSummary.tsx +269 -0
  147. package/src/components/SegmentedTrack/SegmentedTrack.tsx +268 -0
  148. package/src/components/StatGroup/StatGroup.tsx +169 -0
  149. package/src/components/StatItem/StatItem.tsx +117 -40
  150. package/src/components/StrengthIndicator/StrengthIndicator.tsx +205 -0
  151. package/src/components/SummaryTile/SummaryTile.tsx +251 -0
  152. package/src/components/Text/Text.tsx +24 -3
  153. package/src/components/Tooltip/Tooltip.tsx +50 -25
  154. package/src/components/index.ts +47 -3
  155. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  156. package/src/icons/registry.ts +1 -1
  157. package/src/utils/index.ts +1 -0
  158. package/src/utils/number-utils.ts +60 -0
@@ -1,223 +1,373 @@
1
1
  "use strict";
2
2
 
3
- import React, { useState, useMemo, useCallback } from '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 TextInput from '../TextInput/TextInput';
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
- function useFormField(props) {
12
- const {
13
- type = 'text',
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
- // Merge FormField States collection based on focus
32
- const modes = useMemo(() => ({
33
- ...baseModes,
34
- 'FormField States': isFocused ? 'Active' : 'Idle'
35
- }), [baseModes, isFocused]);
36
-
37
- // -- Label tokens (from "FormField / Output" collection) --
38
- const labelColor = getVariableByName('formField/label/color', modes) || '#0c0d10';
39
- const labelFontFamily = getVariableByName('formField/label/fontFamily', modes) || 'JioType Var';
40
- const labelFontSize = parseInt(getVariableByName('formField/label/fontSize', modes), 10) || 14;
41
- const labelLineHeight = parseInt(getVariableByName('formField/label/lineHeight', modes), 10) || 17;
42
- const labelFontWeight = getVariableByName('formField/label/fontWeight', modes) || '500';
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
- modes = EMPTY_MODES,
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: resolvedModes,
153
- labelStyle,
154
- wrapperStyle,
155
- requiredIndicatorStyle,
156
- inputContainerStyle,
157
- inputTextStyle,
158
- supportStatus,
159
- supportLabel,
160
- secureTextEntry,
161
- keyboardType,
162
- autoCapitalize,
163
- handleFocus,
164
- handleBlur
165
- } = useFormField({
166
- type,
167
- isDisabled,
168
- isInvalid,
169
- supportText,
170
- errorMessage,
171
- modes,
172
- onFocus,
173
- onBlur
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
- accessible: true,
180
- accessibilityRole: "none",
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: labelStyle,
331
+ style: labelTextStyle,
192
332
  children: label
193
333
  }), isRequired && /*#__PURE__*/_jsx(Text, {
194
334
  style: requiredIndicatorStyle,
195
335
  children: " *"
196
336
  })]
197
- }), /*#__PURE__*/_jsx(TextInput, {
198
- placeholder: placeholder || '',
199
- value: value,
200
- ...(onChangeText ? {
201
- onChangeText
202
- } : {}),
203
- leading: leading,
204
- trailing: trailing,
205
- leadingIconName: leadingIconName || 'ic_search',
206
- modes: resolvedModes,
207
- style: inputContainerStyle,
208
- inputStyle: inputTextStyle,
209
- onFocus: handleFocus,
210
- onBlur: handleBlur,
211
- secureTextEntry: secureTextEntry,
212
- keyboardType: keyboardType,
213
- autoCapitalize: autoCapitalize,
214
- editable: !isDisabled,
215
- accessibilityLabel: resolvedA11yLabel,
216
- accessibilityHint: accessibilityHint || ''
217
- }), supportLabel != null && /*#__PURE__*/_jsx(SupportText, {
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: resolvedModes
370
+ modes: modes
221
371
  })]
222
372
  });
223
373
  }
@@ -5,6 +5,7 @@ import { View, Text } from 'react-native';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
6
  import { EMPTY_MODES } from '../../utils/react-utils';
7
7
  import MoneyValue from '../MoneyValue/MoneyValue';
8
+ import LinearProgress from '../LinearProgress/LinearProgress';
8
9
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
10
  const LinearMeterLabel = ({
10
11
  children,
@@ -41,14 +42,6 @@ const LinearMeter = ({
41
42
  ...rest
42
43
  }) => {
43
44
  const gap = getVariableByName('linearMeter/gap', modes);
44
- // Track tokens
45
- const trackBg = getVariableByName('linearMeter/track/background', modes);
46
- const trackHeight = getVariableByName('linearMeter/track/height', modes);
47
- const trackRadius = getVariableByName('linearMeter/track/radius', modes);
48
-
49
- // Indicator tokens
50
- const indicatorBg = getVariableByName('linearMeter/indicator/background', modes);
51
- const indicatorRadius = getVariableByName('linearMeter/indicator/radius', modes);
52
45
 
53
46
  // Wrap tokens
54
47
  const wrapGap = getVariableByName('linearMeter/wrap/gap', modes);
@@ -86,10 +79,6 @@ const LinearMeter = ({
86
79
  })]
87
80
  });
88
81
  const content = children ? childrenWithModes : defaultContent;
89
-
90
- // Calculate width percentage
91
- const clampedValue = Math.min(Math.max(value, 0), 1);
92
- const widthPercent = `${clampedValue * 100}%`;
93
82
  return /*#__PURE__*/_jsxs(View, {
94
83
  style: [{
95
84
  flexDirection: 'row',
@@ -97,22 +86,14 @@ const LinearMeter = ({
97
86
  gap
98
87
  }, style],
99
88
  ...rest,
100
- children: [/*#__PURE__*/_jsx(View, {
101
- style: [{
102
- flex: 1,
103
- height: trackHeight,
104
- backgroundColor: trackBg,
105
- borderRadius: trackRadius,
106
- overflow: 'hidden' // Ensure indicator stays inside
107
- }, trackStyle],
108
- children: /*#__PURE__*/_jsx(View, {
109
- style: [{
110
- width: widthPercent,
111
- height: '100%',
112
- backgroundColor: indicatorBg,
113
- borderRadius: indicatorRadius
114
- }, indicatorStyle]
115
- })
89
+ children: [/*#__PURE__*/_jsx(LinearProgress, {
90
+ value: value,
91
+ modes: modes,
92
+ style: {
93
+ flex: 1
94
+ },
95
+ trackStyle: trackStyle,
96
+ indicatorStyle: indicatorStyle
116
97
  }), /*#__PURE__*/_jsx(View, {
117
98
  style: {
118
99
  flexDirection: 'row',
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+
3
+ import React from 'react';
4
+ import { View } from 'react-native';
5
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
+ import { EMPTY_MODES } from '../../utils/react-utils';
7
+ import { jsx as _jsx } from "react/jsx-runtime";
8
+ const LinearProgress = ({
9
+ value = 0,
10
+ modes = EMPTY_MODES,
11
+ style,
12
+ trackStyle,
13
+ indicatorStyle,
14
+ ...rest
15
+ }) => {
16
+ // The track and the progress indicator are intentionally rendered at
17
+ // different emphasis levels by default: the track sits in the
18
+ // background as a low-emphasis surface, while the progress indicator
19
+ // is the high-emphasis foreground. Defaults are placed *before* the
20
+ // user-provided modes spread, so callers can still override
21
+ // `Emphasis / DataViz` (or any other mode) via the `modes` prop.
22
+ const trackModes = {
23
+ 'Emphasis': 'Low',
24
+ ...modes
25
+ };
26
+ const progressModes = {
27
+ 'Emphasis': 'High',
28
+ ...modes
29
+ };
30
+ const trackHeight = getVariableByName('linearProgress/track/height', trackModes) ?? 8;
31
+ const trackRadius = getVariableByName('linearProgress/track/radius', trackModes) ?? 999;
32
+ const trackBg = getVariableByName('linearProgress/track/background', trackModes) ?? '#ede7ff';
33
+ const indicatorHeight = getVariableByName('linearProgress/indicator/height', progressModes) ?? 8;
34
+ const indicatorRadius = getVariableByName('linearProgress/indicator/radius', progressModes) ?? 999;
35
+ const indicatorBg = getVariableByName('linearProgress/indicator/background', progressModes) ?? '#5d00b5';
36
+ const clampedValue = Math.min(Math.max(value, 0), 1);
37
+ const widthPercent = `${clampedValue * 100}%`;
38
+ return /*#__PURE__*/_jsx(View, {
39
+ style: [{
40
+ height: trackHeight,
41
+ backgroundColor: trackBg,
42
+ borderRadius: trackRadius,
43
+ overflow: 'hidden',
44
+ width: '100%'
45
+ }, style, trackStyle],
46
+ accessibilityRole: "progressbar",
47
+ accessibilityValue: {
48
+ min: 0,
49
+ max: 1,
50
+ now: clampedValue
51
+ },
52
+ ...rest,
53
+ children: /*#__PURE__*/_jsx(View, {
54
+ style: [{
55
+ width: widthPercent,
56
+ height: indicatorHeight,
57
+ backgroundColor: indicatorBg,
58
+ borderRadius: indicatorRadius
59
+ }, indicatorStyle]
60
+ })
61
+ });
62
+ };
63
+ export default LinearProgress;