jfs-components 0.0.79 → 0.0.85

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 (138) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/lib/commonjs/components/AppBar/AppBar.js +70 -6
  3. package/lib/commonjs/components/AreaLineChart/AreaLineChart.js +866 -0
  4. package/lib/commonjs/components/AreaLineChart/chartMath.js +252 -0
  5. package/lib/commonjs/components/Attached/Attached.js +76 -7
  6. package/lib/commonjs/components/BubbleChart/BubbleChart.js +191 -0
  7. package/lib/commonjs/components/BubbleChart/bubblePacking.js +378 -0
  8. package/lib/commonjs/components/Checkbox/Checkbox.js +18 -2
  9. package/lib/commonjs/components/ClusterBubble/ClusterBubble.js +272 -0
  10. package/lib/commonjs/components/Drawer/Drawer.js +6 -1
  11. package/lib/commonjs/components/DropdownInput/DropdownInput.js +30 -6
  12. package/lib/commonjs/components/ExpandableCheckbox/ExpandableCheckbox.js +17 -11
  13. package/lib/commonjs/components/FormField/FormField.js +1 -14
  14. package/lib/commonjs/components/FullscreenModal/FullscreenModal.js +5 -1
  15. package/lib/commonjs/components/ListItem/ListItem.js +6 -11
  16. package/lib/commonjs/components/MessageField/MessageField.js +1 -13
  17. package/lib/commonjs/components/MetricLegendItem/MetricLegendItem.js +7 -1
  18. package/lib/commonjs/components/PaymentFeedback/PaymentFeedback.js +12 -9
  19. package/lib/commonjs/components/PlanComparisonCard/PlanComparisonCard.js +69 -160
  20. package/lib/commonjs/components/Spinner/Spinner.js +217 -0
  21. package/lib/commonjs/components/TextInput/TextInput.js +33 -18
  22. package/lib/commonjs/components/index.js +34 -0
  23. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  24. package/lib/commonjs/icons/components/IconArrowdown.js +19 -0
  25. package/lib/commonjs/icons/components/IconArrowup.js +19 -0
  26. package/lib/commonjs/icons/components/IconChevrondowncircle.js +19 -0
  27. package/lib/commonjs/icons/components/IconChevronleftcircle.js +19 -0
  28. package/lib/commonjs/icons/components/IconChevronrightcircle.js +19 -0
  29. package/lib/commonjs/icons/components/IconChevronupcircle.js +19 -0
  30. package/lib/commonjs/icons/components/IconOsnavback.js +19 -0
  31. package/lib/commonjs/icons/components/IconOsnavcenter.js +19 -0
  32. package/lib/commonjs/icons/components/IconOsnavhome.js +19 -0
  33. package/lib/commonjs/icons/components/IconOsnavtask.js +19 -0
  34. package/lib/commonjs/icons/components/IconSignin.js +19 -0
  35. package/lib/commonjs/icons/components/IconSignout.js +19 -0
  36. package/lib/commonjs/icons/components/index.js +132 -0
  37. package/lib/commonjs/icons/registry.js +2 -2
  38. package/lib/module/components/AppBar/AppBar.js +70 -6
  39. package/lib/module/components/AreaLineChart/AreaLineChart.js +859 -0
  40. package/lib/module/components/AreaLineChart/chartMath.js +242 -0
  41. package/lib/module/components/Attached/Attached.js +76 -7
  42. package/lib/module/components/BubbleChart/BubbleChart.js +185 -0
  43. package/lib/module/components/BubbleChart/bubblePacking.js +370 -0
  44. package/lib/module/components/Checkbox/Checkbox.js +18 -2
  45. package/lib/module/components/ClusterBubble/ClusterBubble.js +267 -0
  46. package/lib/module/components/Drawer/Drawer.js +6 -1
  47. package/lib/module/components/DropdownInput/DropdownInput.js +30 -6
  48. package/lib/module/components/ExpandableCheckbox/ExpandableCheckbox.js +17 -11
  49. package/lib/module/components/FormField/FormField.js +3 -16
  50. package/lib/module/components/FullscreenModal/FullscreenModal.js +5 -1
  51. package/lib/module/components/ListItem/ListItem.js +6 -11
  52. package/lib/module/components/MessageField/MessageField.js +3 -15
  53. package/lib/module/components/MetricLegendItem/MetricLegendItem.js +7 -1
  54. package/lib/module/components/PaymentFeedback/PaymentFeedback.js +13 -9
  55. package/lib/module/components/PlanComparisonCard/PlanComparisonCard.js +72 -160
  56. package/lib/module/components/Spinner/Spinner.js +212 -0
  57. package/lib/module/components/TextInput/TextInput.js +34 -19
  58. package/lib/module/components/index.js +4 -0
  59. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  60. package/lib/module/icons/components/IconArrowdown.js +12 -0
  61. package/lib/module/icons/components/IconArrowup.js +12 -0
  62. package/lib/module/icons/components/IconChevrondowncircle.js +12 -0
  63. package/lib/module/icons/components/IconChevronleftcircle.js +12 -0
  64. package/lib/module/icons/components/IconChevronrightcircle.js +12 -0
  65. package/lib/module/icons/components/IconChevronupcircle.js +12 -0
  66. package/lib/module/icons/components/IconOsnavback.js +12 -0
  67. package/lib/module/icons/components/IconOsnavcenter.js +12 -0
  68. package/lib/module/icons/components/IconOsnavhome.js +12 -0
  69. package/lib/module/icons/components/IconOsnavtask.js +12 -0
  70. package/lib/module/icons/components/IconSignin.js +12 -0
  71. package/lib/module/icons/components/IconSignout.js +12 -0
  72. package/lib/module/icons/components/index.js +12 -0
  73. package/lib/module/icons/registry.js +2 -2
  74. package/lib/typescript/src/components/AppBar/AppBar.d.ts +12 -1
  75. package/lib/typescript/src/components/AreaLineChart/AreaLineChart.d.ts +212 -0
  76. package/lib/typescript/src/components/AreaLineChart/chartMath.d.ts +90 -0
  77. package/lib/typescript/src/components/Attached/Attached.d.ts +19 -16
  78. package/lib/typescript/src/components/BubbleChart/BubbleChart.d.ts +81 -0
  79. package/lib/typescript/src/components/BubbleChart/bubblePacking.d.ts +83 -0
  80. package/lib/typescript/src/components/ClusterBubble/ClusterBubble.d.ts +76 -0
  81. package/lib/typescript/src/components/DropdownInput/DropdownInput.d.ts +3 -2
  82. package/lib/typescript/src/components/ListItem/ListItem.d.ts +3 -3
  83. package/lib/typescript/src/components/MetricLegendItem/MetricLegendItem.d.ts +7 -1
  84. package/lib/typescript/src/components/PaymentFeedback/PaymentFeedback.d.ts +5 -1
  85. package/lib/typescript/src/components/PlanComparisonCard/PlanComparisonCard.d.ts +10 -8
  86. package/lib/typescript/src/components/Spinner/Spinner.d.ts +45 -0
  87. package/lib/typescript/src/components/index.d.ts +4 -0
  88. package/lib/typescript/src/icons/components/IconArrowdown.d.ts +3 -0
  89. package/lib/typescript/src/icons/components/IconArrowup.d.ts +3 -0
  90. package/lib/typescript/src/icons/components/IconChevrondowncircle.d.ts +3 -0
  91. package/lib/typescript/src/icons/components/IconChevronleftcircle.d.ts +3 -0
  92. package/lib/typescript/src/icons/components/IconChevronrightcircle.d.ts +3 -0
  93. package/lib/typescript/src/icons/components/IconChevronupcircle.d.ts +3 -0
  94. package/lib/typescript/src/icons/components/IconOsnavback.d.ts +3 -0
  95. package/lib/typescript/src/icons/components/IconOsnavcenter.d.ts +3 -0
  96. package/lib/typescript/src/icons/components/IconOsnavhome.d.ts +3 -0
  97. package/lib/typescript/src/icons/components/IconOsnavtask.d.ts +3 -0
  98. package/lib/typescript/src/icons/components/IconSignin.d.ts +3 -0
  99. package/lib/typescript/src/icons/components/IconSignout.d.ts +3 -0
  100. package/lib/typescript/src/icons/components/index.d.ts +12 -0
  101. package/lib/typescript/src/icons/registry.d.ts +1 -1
  102. package/package.json +3 -2
  103. package/src/components/AppBar/AppBar.tsx +92 -12
  104. package/src/components/AreaLineChart/AreaLineChart.tsx +1161 -0
  105. package/src/components/AreaLineChart/chartMath.ts +265 -0
  106. package/src/components/Attached/Attached.tsx +94 -7
  107. package/src/components/BubbleChart/BubbleChart.tsx +319 -0
  108. package/src/components/BubbleChart/bubblePacking.ts +397 -0
  109. package/src/components/Checkbox/Checkbox.tsx +14 -2
  110. package/src/components/ClusterBubble/ClusterBubble.tsx +359 -0
  111. package/src/components/Drawer/Drawer.tsx +4 -0
  112. package/src/components/DropdownInput/DropdownInput.tsx +54 -20
  113. package/src/components/ExpandableCheckbox/ExpandableCheckbox.tsx +13 -9
  114. package/src/components/FormField/FormField.tsx +3 -19
  115. package/src/components/FullscreenModal/FullscreenModal.tsx +3 -0
  116. package/src/components/ListItem/ListItem.tsx +14 -16
  117. package/src/components/MessageField/MessageField.tsx +3 -18
  118. package/src/components/MetricLegendItem/MetricLegendItem.tsx +20 -6
  119. package/src/components/PaymentFeedback/PaymentFeedback.tsx +15 -8
  120. package/src/components/PlanComparisonCard/PlanComparisonCard.tsx +82 -192
  121. package/src/components/Spinner/Spinner.tsx +273 -0
  122. package/src/components/TextInput/TextInput.tsx +37 -19
  123. package/src/components/index.ts +4 -0
  124. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  125. package/src/icons/components/IconArrowdown.tsx +11 -0
  126. package/src/icons/components/IconArrowup.tsx +11 -0
  127. package/src/icons/components/IconChevrondowncircle.tsx +11 -0
  128. package/src/icons/components/IconChevronleftcircle.tsx +11 -0
  129. package/src/icons/components/IconChevronrightcircle.tsx +11 -0
  130. package/src/icons/components/IconChevronupcircle.tsx +11 -0
  131. package/src/icons/components/IconOsnavback.tsx +11 -0
  132. package/src/icons/components/IconOsnavcenter.tsx +11 -0
  133. package/src/icons/components/IconOsnavhome.tsx +11 -0
  134. package/src/icons/components/IconOsnavtask.tsx +11 -0
  135. package/src/icons/components/IconSignin.tsx +11 -0
  136. package/src/icons/components/IconSignout.tsx +11 -0
  137. package/src/icons/components/index.ts +12 -0
  138. package/src/icons/registry.ts +49 -1
@@ -4,14 +4,16 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
- var _react = _interopRequireWildcard(require("react"));
7
+ var _react = _interopRequireDefault(require("react"));
8
8
  var _reactNative = require("react-native");
9
9
  var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
10
10
  var _reactUtils = require("../../utils/react-utils");
11
11
  var _Icon = _interopRequireDefault(require("../../icons/Icon"));
12
12
  var _jsxRuntime = require("react/jsx-runtime");
13
13
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
14
- function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
14
+ /** Figma grid: label column 1.8fr, each plan column 1fr. */const LABEL_COLUMN_FR = 1.8;
15
+ const PLAN_COLUMN_FR = 1;
16
+
15
17
  /**
16
18
  * A single plan column header (the label column has no header of its own).
17
19
  */
@@ -43,121 +45,65 @@ const DEFAULT_ROWS = [{
43
45
  values: [false, '1%']
44
46
  }];
45
47
 
48
+ /** Keeps every text layer on a single line. */
49
+ const NO_WRAP_TEXT = {
50
+ flexShrink: 0,
51
+ ...(_reactNative.Platform.OS === 'web' ? {
52
+ whiteSpace: 'nowrap'
53
+ } : {})
54
+ };
55
+ const labelColumnStyle = {
56
+ flex: LABEL_COLUMN_FR,
57
+ minWidth: 0
58
+ };
59
+ const planColumnStyle = {
60
+ flex: PLAN_COLUMN_FR,
61
+ minWidth: 0,
62
+ alignItems: 'center'
63
+ };
64
+
46
65
  /**
47
66
  * PlanComparisonCard renders a compact comparison table that pits the user's
48
67
  * current plan against one or more alternative plans across a set of feature
49
68
  * rows. Implementation of Figma node `4498:2968` (`PlanComparisonCard`).
50
69
  *
51
- * The leading column holds feature labels (with an optional info icon); every
52
- * other column maps to a plan in `columns`. Each cell value can be plain text,
53
- * a "not available" cross (`false`), or any custom React node.
70
+ * Columns use a 1.8fr / 1fr flex ratio (label vs plan), matching the Figma grid.
54
71
  *
55
72
  * @component
56
- * @example
57
- * ```tsx
58
- * <PlanComparisonCard
59
- * columns={[{ label: 'Your plan' }, { label: 'JioFinance+', brand: true }]}
60
- * rows={[
61
- * { label: 'JioPoints multiplier', values: ['1x', '1.25x'] },
62
- * { label: 'Cashback', showInfo: true, values: [false, 'Upto ₹5000'] },
63
- * ]}
64
- * />
65
- * ```
66
73
  */
67
- /** Keeps every text layer on a single line; columns grow to fit content. */
68
- const NO_WRAP_TEXT = {
69
- flexShrink: 0,
70
- ...(_reactNative.Platform.OS === 'web' ? {
71
- whiteSpace: 'nowrap'
72
- } : {})
73
- };
74
74
  function PlanComparisonCard({
75
75
  columns = DEFAULT_COLUMNS,
76
76
  rows = DEFAULT_ROWS,
77
- labelColumnFlex = 0,
78
77
  modes = _reactUtils.EMPTY_MODES,
79
78
  style
80
79
  }) {
81
- /** Natural widths from header labels (plan columns only). */
82
- const [headerWidths, setHeaderWidths] = (0, _react.useState)([]);
83
- /** Natural widths from table body columns. */
84
- const [bodyWidths, setBodyWidths] = (0, _react.useState)([]);
85
- const setMeasuredWidth = (0, _react.useCallback)((setter, index, width) => {
86
- setter(prev => {
87
- if (prev[index] === width) return prev;
88
- const next = [...prev];
89
- next[index] = width;
90
- return next;
91
- });
92
- }, []);
93
- const onHeaderColumnLayout = (0, _react.useCallback)((index, event) => {
94
- setMeasuredWidth(setHeaderWidths, index, event.nativeEvent.layout.width);
95
- }, [setMeasuredWidth]);
96
- const onBodyColumnLayout = (0, _react.useCallback)((index, event) => {
97
- setMeasuredWidth(setBodyWidths, index, event.nativeEvent.layout.width);
98
- }, [setMeasuredWidth]);
99
-
100
- /**
101
- * Shared width for header + body cells in a column (max of natural header
102
- * label vs body content). No columnGap between columns — gaps would shift
103
- * headers relative to the flush table grid below.
104
- */
105
- const columnWidthStyle = index => {
106
- const width = Math.max(headerWidths[index] ?? 0, bodyWidths[index] ?? 0);
107
- if (width > 0) {
108
- return {
109
- width,
110
- minWidth: width,
111
- flexShrink: 0,
112
- flexGrow: 0
113
- };
114
- }
115
- return {
116
- flexShrink: 0,
117
- flexGrow: 0
118
- };
119
- };
120
-
121
- // Container
122
80
  const gap = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/gap', modes) ?? 16;
123
-
124
- // Header
125
81
  const headerFg = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/header/fg', modes) ?? '#ffffff';
126
82
  const headerBrandFg = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/header/brand/fg', modes) ?? '#cea15a';
127
83
  const headerFontSize = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/header/fontSize', modes) ?? 14;
128
84
  const headerFontFamily = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/header/fontFamily', modes) ?? 'JioType Var';
129
85
  const headerLineHeight = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/header/lineHeight', modes) ?? 18;
130
86
  const headerFontWeight = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/header/fontWeight', modes) ?? '500';
131
-
132
- // Table
133
87
  const tableBackground = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/tableRow/background', modes) ?? '#141414';
134
88
  const tableRadius = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/tableRow/radius', modes) ?? 16;
135
89
  const tableBorderSize = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/tableRow/border/size', modes) ?? 1;
136
90
  const tableBorderColor = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/tableRow/border/color', modes) ?? '#1e1a14';
137
-
138
- // Cell
139
91
  const cellPadding = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/tableCell/padding', modes) ?? 12;
140
92
  const cellGap = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/tableCell/gap', modes) ?? 2;
141
93
  const cellMinHeight = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/tableCell/height', modes) ?? 46;
142
94
  const cellBorderSize = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/tableCell/border/size', modes) ?? 1;
143
95
  const cellBorderColor = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/tableCell/border/color', modes) ?? '#1e1a14';
144
-
145
- // Cell label
146
96
  const labelColor = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/tableCell/label/color', modes) ?? '#ffffff';
147
97
  const labelDisabledColor = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/tableCell/label/disabled/color', modes) ?? '#91949c';
148
98
  const labelFontSize = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/tableCell/label/fontSize', modes) ?? 12;
149
99
  const labelFontFamily = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/tableCell/label/fontFamily', modes) ?? 'JioType Var';
150
100
  const labelLineHeight = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/tableCell/label/lineHeight', modes) ?? 16;
151
101
  const labelFontWeight = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/tableCell/label/fontWeight', modes) ?? '400';
152
-
153
- // Cell value
154
102
  const valueColor = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/tableCell/value/color', modes) ?? '#ffffff';
155
103
  const valueFontSize = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/tableCell/value/fontSize', modes) ?? 12;
156
104
  const valueFontFamily = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/tableCell/value/fontFamily', modes) ?? 'JioType Var';
157
105
  const valueLineHeight = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/tableCell/value/lineHeight', modes) ?? 16;
158
106
  const valueFontWeight = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/tableCell/value/fontWeight', modes) ?? '500';
159
-
160
- // Icon
161
107
  const iconColor = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/icon/color', modes) ?? '#ffffff';
162
108
  const iconSize = (0, _figmaVariablesResolver.getVariableByName)('planComparisonCard/icon/size', modes) ?? 16;
163
109
  const toWeight = w => typeof w === 'number' ? `${w}` : w;
@@ -186,12 +132,26 @@ function PlanComparisonCard({
186
132
  fontWeight: toWeight(valueFontWeight),
187
133
  textAlign: 'center'
188
134
  };
189
- const planHeaderColumnStyle = {
135
+ const rowStyle = {
136
+ flexDirection: 'row',
137
+ width: '100%'
138
+ };
139
+ const labelCellStyle = {
140
+ flexDirection: 'row',
190
141
  alignItems: 'center',
191
- justifyContent: 'center'
142
+ gap: cellGap,
143
+ padding: cellPadding,
144
+ minHeight: cellMinHeight
145
+ };
146
+ const valueCellStyle = {
147
+ flexDirection: 'row',
148
+ alignItems: 'center',
149
+ justifyContent: 'center',
150
+ padding: cellPadding,
151
+ minHeight: cellMinHeight,
152
+ width: '100%'
192
153
  };
193
154
  const renderValue = (value, cellKey) => {
194
- // "Not available" → muted cross icon.
195
155
  if (value === false) {
196
156
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Icon.default, {
197
157
  name: "ic_close",
@@ -199,87 +159,54 @@ function PlanComparisonCard({
199
159
  color: labelDisabledColor
200
160
  }, cellKey);
201
161
  }
202
- // Empty cell.
203
162
  if (value === null || value === undefined || value === true) {
204
163
  return null;
205
164
  }
206
- // Text content.
207
165
  if (typeof value === 'string' || typeof value === 'number') {
208
166
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
209
167
  style: valueTextStyle,
210
168
  children: value
211
169
  }, cellKey);
212
170
  }
213
- // Custom node — forward modes so themed children stay in sync.
214
171
  return (0, _reactUtils.cloneChildrenWithModes)(value, modes);
215
172
  };
216
- const labelCellStyle = {
217
- flexDirection: 'row',
218
- alignItems: 'center',
219
- gap: cellGap,
220
- padding: cellPadding,
221
- minHeight: cellMinHeight,
222
- flexShrink: 0
223
- };
224
- const valueCellStyle = {
225
- flexDirection: 'row',
226
- alignItems: 'center',
227
- justifyContent: 'center',
228
- padding: cellPadding,
229
- minHeight: cellMinHeight,
230
- flexShrink: 0
231
- };
232
173
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
233
174
  style: [{
234
175
  gap,
235
- alignSelf: 'flex-start'
176
+ width: '100%'
236
177
  }, style],
237
178
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
238
- style: {
239
- flexDirection: 'row',
240
- alignItems: 'flex-end'
241
- },
179
+ style: rowStyle,
242
180
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
243
- style: [columnWidthStyle(0), labelColumnFlex > 0 ? {
244
- flexGrow: labelColumnFlex
245
- } : undefined]
246
- }), columns.map((column, index) => {
247
- const colIndex = index + 1;
248
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
249
- onLayout: e => onHeaderColumnLayout(colIndex, e),
250
- style: [columnWidthStyle(colIndex), planHeaderColumnStyle],
251
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
252
- style: [headerTextStyle, {
253
- color: column.brand ? headerBrandFg : headerFg,
254
- alignSelf: 'center'
255
- }],
256
- children: column.label
257
- })
258
- }, column.label ?? index);
259
- })]
260
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
181
+ style: labelColumnStyle
182
+ }), columns.map((column, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
183
+ style: planColumnStyle,
184
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
185
+ style: [headerTextStyle, {
186
+ color: column.brand ? headerBrandFg : headerFg
187
+ }],
188
+ children: column.label
189
+ })
190
+ }, column.label ?? index))]
191
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
261
192
  style: {
262
- flexDirection: 'row',
263
- alignSelf: 'flex-start',
193
+ width: '100%',
264
194
  backgroundColor: tableBackground,
265
195
  borderWidth: tableBorderSize,
266
196
  borderColor: tableBorderColor,
267
197
  borderRadius: tableRadius,
268
198
  overflow: 'hidden'
269
199
  },
270
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
271
- onLayout: e => onBodyColumnLayout(0, e),
272
- style: [columnWidthStyle(0), labelColumnFlex > 0 ? {
273
- flexGrow: labelColumnFlex
274
- } : undefined],
275
- children: rows.map((row, rowIndex) => {
276
- const isLast = rowIndex === rows.length - 1;
277
- const showInfo = row.showInfo || row.onInfoPress != null;
278
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
279
- style: [labelCellStyle, {
280
- borderBottomWidth: isLast ? 0 : cellBorderSize,
281
- borderBottomColor: cellBorderColor
282
- }],
200
+ children: rows.map((row, rowIndex) => {
201
+ const isLast = rowIndex === rows.length - 1;
202
+ const showInfo = row.showInfo || row.onInfoPress != null;
203
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
204
+ style: [rowStyle, {
205
+ borderBottomWidth: isLast ? 0 : cellBorderSize,
206
+ borderBottomColor: cellBorderColor
207
+ }],
208
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
209
+ style: [labelColumnStyle, labelCellStyle],
283
210
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
284
211
  style: labelTextStyle,
285
212
  children: row.label
@@ -298,30 +225,12 @@ function PlanComparisonCard({
298
225
  size: iconSize,
299
226
  color: iconColor
300
227
  }))]
301
- }, row.key ?? `${row.label}-${rowIndex}`);
302
- })
303
- }), columns.map((column, colIndex) => {
304
- const colIndexWidth = colIndex + 1;
305
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
306
- onLayout: e => onBodyColumnLayout(colIndexWidth, e),
307
- style: [columnWidthStyle(colIndexWidth), planHeaderColumnStyle],
308
- children: rows.map((row, rowIndex) => {
309
- const isLast = rowIndex === rows.length - 1;
310
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
311
- style: [valueCellStyle, {
312
- borderBottomWidth: isLast ? 0 : cellBorderSize,
313
- borderBottomColor: cellBorderColor
314
- }],
315
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
316
- style: {
317
- flexShrink: 0
318
- },
319
- children: renderValue(row.values?.[colIndex], `${rowIndex}-${colIndex}`)
320
- })
321
- }, row.key ?? `${row.label}-${rowIndex}`);
322
- })
323
- }, column.label ?? colIndex);
324
- })]
228
+ }), columns.map((column, colIndex) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
229
+ style: [planColumnStyle, valueCellStyle],
230
+ children: renderValue(row.values?.[colIndex], `${rowIndex}-${colIndex}`)
231
+ }, column.label ?? colIndex))]
232
+ }, row.key ?? `${row.label}-${rowIndex}`);
233
+ })
325
234
  })]
326
235
  });
327
236
  }
@@ -0,0 +1,217 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _reactNative = require("react-native");
9
+ var _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated"));
10
+ var _reactNativeSvg = _interopRequireWildcard(require("react-native-svg"));
11
+ var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
12
+ var _JFSThemeProvider = require("../../design-tokens/JFSThemeProvider");
13
+ var _reactUtils = require("../../utils/react-utils");
14
+ var _useReducedMotion = require("../../skeleton/useReducedMotion");
15
+ var _jsxRuntime = require("react/jsx-runtime");
16
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
17
+ /**
18
+ * Per-segment colours, resolved from the Figma `spiner/*` tokens. Consumers can
19
+ * override any subset via the `colors` prop.
20
+ */
21
+
22
+ const SEGMENT_COUNT = 3;
23
+ const DEFAULT_SIZE = 72;
24
+ const DEFAULT_DURATION_MS = 1500;
25
+ const DEFAULT_GRAVITY = 0.45;
26
+
27
+ // Stroke thickness as a fraction of the diameter (matches the Figma ring weight).
28
+ const STROKE_RATIO = 0.11;
29
+ // Angular length of each individual segment.
30
+ const ARC_LENGTH_DEG = 100;
31
+ // Spacing between consecutive heads when fully bunched at the top. Small but
32
+ // non-zero so all three colours stay faintly visible as they crest the top.
33
+ const SPREAD_MIN_DEG = 10;
34
+ // Spacing between consecutive heads at full spread. At this extent each segment's
35
+ // tail only overlaps the next head by `ARC_LENGTH_DEG - SPREAD_MAX_DEG` (16°) —
36
+ // the maximum extension the chain reaches while staying connected (never a gap).
37
+ const SPREAD_MAX_DEG = 84;
38
+ // Fraction of each revolution spent gradually fanning *out* (the rest is spent
39
+ // snapping back together over the top).
40
+ //
41
+ // This is the knob that balances "reaches full extension" against "never stalls
42
+ // and never recoils". The tail segment's velocity while spreading is
43
+ // `vLead * (1 - spreadRange / (SPREAD_OUT_FRAC * π))`. Spreading the fan-out over
44
+ // ~3/4 of the turn keeps that factor around ~0.45 (so the tail always carries
45
+ // clear forward momentum — it never crawls to a stall, and never reverses),
46
+ // while still letting the breath reach a full 1.0. The remaining ~1/4 is an
47
+ // energetic gather over the top where the trailing segments whip forward to
48
+ // rejoin the lead. A symmetric (sinusoidal/triangle) breath cannot do all three:
49
+ // reach full extension, avoid recoil, and avoid a sustained stall.
50
+ const SPREAD_OUT_FRAC = 0.75;
51
+ const DEG_TO_RAD = Math.PI / 180;
52
+ const TWO_PI = Math.PI * 2;
53
+ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
54
+ const toNumber = (value, fallback) => {
55
+ if (typeof value === 'number' && Number.isFinite(value)) {
56
+ return value;
57
+ }
58
+ if (typeof value === 'string') {
59
+ const parsed = Number(value);
60
+ if (Number.isFinite(parsed)) {
61
+ return parsed;
62
+ }
63
+ }
64
+ return fallback;
65
+ };
66
+
67
+ /**
68
+ * Builds the SVG path for a single fixed-length arc whose *head* sits at the
69
+ * top (12 o'clock) and whose body trails counter-clockwise behind it. Rotating
70
+ * the containing view clockwise then places the head at the desired angle.
71
+ */
72
+ const buildArcPath = (center, radius, arcLengthDeg) => {
73
+ const arc = arcLengthDeg * DEG_TO_RAD;
74
+ // Head at the top: phi = 0 -> (center, center - radius).
75
+ const headX = center;
76
+ const headY = center - radius;
77
+ // Tail trails counter-clockwise by `arc`: phi = -arc.
78
+ const tailX = center + radius * Math.sin(-arc);
79
+ const tailY = center - radius * Math.cos(-arc);
80
+ const largeArc = arcLengthDeg > 180 ? 1 : 0;
81
+ // Sweep from tail -> head is clockwise (sweep flag = 1 in SVG y-down space).
82
+ return `M ${tailX} ${tailY} A ${radius} ${radius} 0 ${largeArc} 1 ${headX} ${headY}`;
83
+ };
84
+
85
+ /**
86
+ * Animated rotation for one segment.
87
+ *
88
+ * A single linear clock drives a gravity-warped lead angle: it advances faster
89
+ * over the top and slower through the bottom, giving the fall its weight. Each
90
+ * segment trails the lead by `index * offset`, where `offset` breathes between
91
+ * its bunched (top) and spread (bottom) extents in lock-step with the lead's
92
+ * vertical position. Because the offset is bounded by `SPREAD_MAX_DEG`, the
93
+ * three segments form a continuously-overlapping chain that gathers at the top
94
+ * and fans out — fully connected — through the free fall.
95
+ */
96
+ const useSegmentRotation = (clock, index, gravity, spreadMinRad, spreadMaxRad, spreadOutFrac) => (0, _reactNativeReanimated.useAnimatedStyle)(() => {
97
+ 'worklet';
98
+
99
+ const tau = clock.value * TWO_PI;
100
+ // Lead angle (clockwise from top). d(lead)/dtau = 1 + gravity*cos(tau) is
101
+ // maximal at the top (tau = 0) and minimal at the bottom (tau = PI), giving
102
+ // the fall its weight.
103
+ const lead = tau + gravity * Math.sin(tau);
104
+ // Breathing is an asymmetric saw in the lead angle: it ramps *gradually* from
105
+ // 0 (bunched, top) up to 1 (fully spread) over `spreadOutFrac` of the turn,
106
+ // then drops back to 0 over the remaining arc (the quick gather over the top).
107
+ // The gentle fan-out slope keeps the trailing segment moving forward at a
108
+ // healthy fraction of the lead's speed — it never stalls and never recoils —
109
+ // while still reaching full extension; the steeper gather is a forward whip,
110
+ // so momentum only ever increases there.
111
+ const leadMod = lead - TWO_PI * Math.floor(lead / TWO_PI);
112
+ const splitLead = spreadOutFrac * TWO_PI;
113
+ const breath = leadMod < splitLead ? leadMod / splitLead : (TWO_PI - leadMod) / (TWO_PI - splitLead);
114
+ const offset = spreadMinRad + breath * (spreadMaxRad - spreadMinRad);
115
+ const head = lead - index * offset;
116
+ return {
117
+ transform: [{
118
+ rotate: `${head * 180 / Math.PI}deg`
119
+ }]
120
+ };
121
+ }, [gravity, index, spreadMinRad, spreadMaxRad, spreadOutFrac]);
122
+ const fullSize = {
123
+ ..._reactNative.StyleSheet.absoluteFillObject
124
+ };
125
+ function Spinner({
126
+ size = DEFAULT_SIZE,
127
+ durationMs = DEFAULT_DURATION_MS,
128
+ gravity = DEFAULT_GRAVITY,
129
+ colors,
130
+ animating = true,
131
+ modes: propModes = _reactUtils.EMPTY_MODES,
132
+ style,
133
+ accessibilityLabel = 'Loading',
134
+ ...rest
135
+ }) {
136
+ const {
137
+ modes: globalModes
138
+ } = (0, _JFSThemeProvider.useTokens)();
139
+ const modes = {
140
+ ...globalModes,
141
+ ...propModes
142
+ };
143
+ const systemReducedMotion = (0, _useReducedMotion.useReducedMotion)();
144
+ const isAnimated = animating && !systemReducedMotion;
145
+ const resolvedSize = toNumber(size, DEFAULT_SIZE);
146
+ const safeGravity = clamp(toNumber(gravity, DEFAULT_GRAVITY), 0, 0.9);
147
+ const strokeWidth = Math.max(1, resolvedSize * STROKE_RATIO);
148
+ const radius = Math.max(0, (resolvedSize - strokeWidth) / 2);
149
+ const center = resolvedSize / 2;
150
+ const arcPath = buildArcPath(center, radius, ARC_LENGTH_DEG);
151
+ const segmentColors = [colors?.primary ?? (0, _figmaVariablesResolver.getVariableByName)('spiner/primary/bg', modes) ?? '#d0a259', colors?.secondary ?? (0, _figmaVariablesResolver.getVariableByName)('spiner/secondary/bg', modes) ?? '#5b00b5', colors?.tertiary ?? (0, _figmaVariablesResolver.getVariableByName)('spiner/tertiary/bg', modes) ?? '#066b99'];
152
+ const clock = (0, _reactNativeReanimated.useSharedValue)(0);
153
+ (0, _react.useEffect)(() => {
154
+ if (!isAnimated) {
155
+ (0, _reactNativeReanimated.cancelAnimation)(clock);
156
+ clock.value = 0;
157
+ return;
158
+ }
159
+ clock.value = 0;
160
+ clock.value = (0, _reactNativeReanimated.withRepeat)((0, _reactNativeReanimated.withTiming)(1, {
161
+ duration: Math.max(1, durationMs),
162
+ easing: _reactNativeReanimated.Easing.linear
163
+ }), -1, false);
164
+ return () => {
165
+ (0, _reactNativeReanimated.cancelAnimation)(clock);
166
+ };
167
+ }, [isAnimated, durationMs, clock]);
168
+
169
+ // Hooks must run unconditionally and in a stable order, so all three segment
170
+ // styles are always computed even when the spinner renders statically.
171
+ const spreadMinRad = SPREAD_MIN_DEG * DEG_TO_RAD;
172
+ const spreadMaxRad = SPREAD_MAX_DEG * DEG_TO_RAD;
173
+ const style0 = useSegmentRotation(clock, 0, safeGravity, spreadMinRad, spreadMaxRad, SPREAD_OUT_FRAC);
174
+ const style1 = useSegmentRotation(clock, 1, safeGravity, spreadMinRad, spreadMaxRad, SPREAD_OUT_FRAC);
175
+ const style2 = useSegmentRotation(clock, 2, safeGravity, spreadMinRad, spreadMaxRad, SPREAD_OUT_FRAC);
176
+ const animatedStyles = [style0, style1, style2];
177
+
178
+ // Static resting fan (evenly spaced) used when animation is disabled.
179
+ const restingRotations = [0, -120, -240];
180
+ const containerStyle = {
181
+ height: resolvedSize,
182
+ width: resolvedSize,
183
+ position: 'relative'
184
+ };
185
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
186
+ accessibilityRole: "progressbar",
187
+ accessibilityLabel: accessibilityLabel,
188
+ style: [containerStyle, style],
189
+ ...rest,
190
+ children: Array.from({
191
+ length: SEGMENT_COUNT
192
+ }, (_, i) => SEGMENT_COUNT - 1 - i).map(segmentIndex => {
193
+ const segmentStyle = isAnimated ? animatedStyles[segmentIndex] : {
194
+ transform: [{
195
+ rotate: `${restingRotations[segmentIndex]}deg`
196
+ }]
197
+ };
198
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
199
+ style: [fullSize, segmentStyle],
200
+ pointerEvents: "none",
201
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.default, {
202
+ width: resolvedSize,
203
+ height: resolvedSize,
204
+ viewBox: `0 0 ${resolvedSize} ${resolvedSize}`,
205
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Path, {
206
+ d: arcPath,
207
+ stroke: segmentColors[segmentIndex],
208
+ strokeWidth: strokeWidth,
209
+ strokeLinecap: "round",
210
+ fill: "none"
211
+ })
212
+ })
213
+ }, segmentIndex);
214
+ })
215
+ });
216
+ }
217
+ var _default = exports.default = Spinner;
@@ -43,7 +43,8 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
43
43
  /**
44
44
  * Helper function to convert a color to a more transparent version for placeholder text.
45
45
  * Takes a color string (hex, rgb, rgba) and returns it with reduced opacity.
46
- */function makePlaceholderColor(color, opacity = 0.5) {
46
+ */const IS_WEB = _reactNative.Platform.OS === 'web';
47
+ function makePlaceholderColor(color, opacity = 0.5) {
47
48
  if (!color || typeof color !== 'string') {
48
49
  return color || '';
49
50
  }
@@ -118,10 +119,9 @@ function TextInput({
118
119
  // Track focus state to hide placeholder when focused
119
120
  const [isFocused, setIsFocused] = (0, _react.useState)(false);
120
121
  const [isHovered, setIsHovered] = (0, _react.useState)(false);
121
- // Ref to the underlying native input so a tap anywhere inside the Pressable
122
- // wrapper can programmatically focus it. Without this, on Android the
123
- // wrapping Pressable becomes the touch responder on the first tap and the
124
- // native input only gains focus on the *second* tap.
122
+ // On web we keep a ref so a click anywhere inside the (Pressable) wrapper can
123
+ // focus the input. On native the wrapper is a plain View and the native
124
+ // input focuses itself on the first tap (see container note below).
125
125
  const inputRef = (0, _react.useRef)(null);
126
126
 
127
127
  // Resolve container tokens
@@ -213,19 +213,18 @@ function TextInput({
213
213
 
214
214
  // Generate default accessibility label from placeholder if not provided
215
215
  const defaultAccessibilityLabel = accessibilityLabel || placeholder || 'Text input';
216
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
217
- style: [containerStyle, focusContainerStyle, hoverStyle, style],
218
- onHoverIn: () => setIsHovered(true),
219
- onHoverOut: () => setIsHovered(false)
220
- // Forward taps on the wrapper (padding, leading icon gutter, etc.) to the
221
- // native input. This guarantees the keyboard opens on the FIRST tap on
222
- // Android instead of requiring a second tap.
223
- ,
224
- onPress: () => inputRef.current?.focus()
225
- // The native input is the real accessible element; don't add a redundant
226
- // focusable node for screen readers.
227
- ,
228
- accessible: false,
216
+
217
+ // IMPORTANT (Android focus reliability):
218
+ // Do NOT wrap the native <TextInput> in a Pressable/Touchable on native.
219
+ // A touch-responder-claiming wrapper steals the first tap, which is the
220
+ // classic cause of the "needs 2–3 taps to focus" Android bug — and forwarding
221
+ // focus from `onPress` is unreliable because the press is cancelled by the
222
+ // tiniest finger movement. A plain <View> does not claim the responder, so
223
+ // the native input receives the tap and focuses on the FIRST tap.
224
+ // On web there is no such issue, so we keep the Pressable for the hover
225
+ // affordance plus click-anywhere-to-focus.
226
+ const containerStyleArray = [containerStyle, focusContainerStyle, hoverStyle, style];
227
+ const inner = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
229
228
  children: [processedLeading && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
230
229
  accessibilityElementsHidden: true,
231
230
  importantForAccessibility: "no",
@@ -248,6 +247,22 @@ function TextInput({
248
247
  children: processedTrailing
249
248
  })]
250
249
  });
250
+ if (IS_WEB) {
251
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
252
+ style: containerStyleArray,
253
+ onHoverIn: () => setIsHovered(true),
254
+ onHoverOut: () => setIsHovered(false)
255
+ // Web: clicking the padding / icon gutter focuses the input too.
256
+ ,
257
+ onPress: () => inputRef.current?.focus(),
258
+ accessible: false,
259
+ children: inner
260
+ });
261
+ }
262
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
263
+ style: containerStyleArray,
264
+ children: inner
265
+ });
251
266
  }
252
267
 
253
268
  /**