jfs-components 0.0.72 → 0.0.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/lib/commonjs/components/AccordionCheckbox/AccordionCheckbox.js +239 -0
- package/lib/commonjs/components/BrandChip/BrandChip.js +149 -0
- package/lib/commonjs/components/CardBankAccount/CardBankAccount.js +213 -0
- package/lib/commonjs/components/CardInsight/CardInsight.js +166 -0
- package/lib/commonjs/components/CheckboxGroup/CheckboxGroup.js +67 -0
- package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +125 -0
- package/lib/commonjs/components/CircularProgressBar/CircularProgressBar.js +56 -9
- package/lib/commonjs/components/CoverageBarComparison/CoverageBarComparison.js +272 -0
- package/lib/commonjs/components/CoverageRing/CoverageRing.js +141 -0
- package/lib/commonjs/components/DonutChart/DonutChart.js +309 -0
- package/lib/commonjs/components/DonutChartSummary/DonutChartSummary.js +155 -0
- package/lib/commonjs/components/LinearMeter/LinearMeter.js +9 -28
- package/lib/commonjs/components/LinearProgress/LinearProgress.js +68 -0
- package/lib/commonjs/components/MetricLegendItem/MetricLegendItem.js +95 -0
- package/lib/commonjs/components/MonthlyStatusGrid/MonthlyStatusGrid.js +286 -0
- package/lib/commonjs/components/OTP/OTP.js +381 -37
- package/lib/commonjs/components/ProductOverview/ProductOverview.js +147 -0
- package/lib/commonjs/components/RangeTrack/RangeTrack.js +269 -0
- package/lib/commonjs/components/SavingsGoalSummary/SavingsGoalSummary.js +181 -0
- package/lib/commonjs/components/SegmentedTrack/SegmentedTrack.js +171 -0
- package/lib/commonjs/components/StatGroup/StatGroup.js +128 -0
- package/lib/commonjs/components/StatItem/StatItem.js +65 -35
- package/lib/commonjs/components/StrengthIndicator/StrengthIndicator.js +157 -0
- package/lib/commonjs/components/SummaryTile/SummaryTile.js +150 -0
- package/lib/commonjs/components/index.js +171 -1
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/commonjs/utils/index.js +7 -0
- package/lib/commonjs/utils/number-utils.js +57 -0
- package/lib/module/components/AccordionCheckbox/AccordionCheckbox.js +233 -0
- package/lib/module/components/BrandChip/BrandChip.js +143 -0
- package/lib/module/components/CardBankAccount/CardBankAccount.js +208 -0
- package/lib/module/components/CardInsight/CardInsight.js +161 -0
- package/lib/module/components/CheckboxGroup/CheckboxGroup.js +62 -0
- package/lib/module/components/CheckboxItem/CheckboxItem.js +119 -0
- package/lib/module/components/CircularProgressBar/CircularProgressBar.js +56 -9
- package/lib/module/components/CoverageBarComparison/CoverageBarComparison.js +266 -0
- package/lib/module/components/CoverageRing/CoverageRing.js +136 -0
- package/lib/module/components/DonutChart/DonutChart.js +303 -0
- package/lib/module/components/DonutChartSummary/DonutChartSummary.js +150 -0
- package/lib/module/components/LinearMeter/LinearMeter.js +9 -28
- package/lib/module/components/LinearProgress/LinearProgress.js +63 -0
- package/lib/module/components/MetricLegendItem/MetricLegendItem.js +90 -0
- package/lib/module/components/MonthlyStatusGrid/MonthlyStatusGrid.js +281 -0
- package/lib/module/components/OTP/OTP.js +381 -38
- package/lib/module/components/ProductOverview/ProductOverview.js +142 -0
- package/lib/module/components/RangeTrack/RangeTrack.js +263 -0
- package/lib/module/components/SavingsGoalSummary/SavingsGoalSummary.js +175 -0
- package/lib/module/components/SegmentedTrack/SegmentedTrack.js +166 -0
- package/lib/module/components/StatGroup/StatGroup.js +123 -0
- package/lib/module/components/StatItem/StatItem.js +66 -36
- package/lib/module/components/StrengthIndicator/StrengthIndicator.js +152 -0
- package/lib/module/components/SummaryTile/SummaryTile.js +145 -0
- package/lib/module/components/index.js +21 -1
- package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/module/icons/registry.js +1 -1
- package/lib/module/utils/index.js +2 -1
- package/lib/module/utils/number-utils.js +53 -0
- package/lib/typescript/src/components/AccordionCheckbox/AccordionCheckbox.d.ts +71 -0
- package/lib/typescript/src/components/BrandChip/BrandChip.d.ts +43 -0
- package/lib/typescript/src/components/CardBankAccount/CardBankAccount.d.ts +79 -0
- package/lib/typescript/src/components/CardInsight/CardInsight.d.ts +48 -0
- package/lib/typescript/src/components/CheckboxGroup/CheckboxGroup.d.ts +41 -0
- package/lib/typescript/src/components/CheckboxItem/CheckboxItem.d.ts +56 -0
- package/lib/typescript/src/components/CircularProgressBar/CircularProgressBar.d.ts +11 -1
- package/lib/typescript/src/components/CoverageBarComparison/CoverageBarComparison.d.ts +105 -0
- package/lib/typescript/src/components/CoverageRing/CoverageRing.d.ts +90 -0
- package/lib/typescript/src/components/DonutChart/DonutChart.d.ts +117 -0
- package/lib/typescript/src/components/DonutChartSummary/DonutChartSummary.d.ts +103 -0
- package/lib/typescript/src/components/LinearProgress/LinearProgress.d.ts +17 -0
- package/lib/typescript/src/components/MetricLegendItem/MetricLegendItem.d.ts +37 -0
- package/lib/typescript/src/components/MonthlyStatusGrid/MonthlyStatusGrid.d.ts +119 -0
- package/lib/typescript/src/components/OTP/OTP.d.ts +88 -2
- package/lib/typescript/src/components/ProductOverview/ProductOverview.d.ts +39 -0
- package/lib/typescript/src/components/RangeTrack/RangeTrack.d.ts +173 -0
- package/lib/typescript/src/components/SavingsGoalSummary/SavingsGoalSummary.d.ts +95 -0
- package/lib/typescript/src/components/SegmentedTrack/SegmentedTrack.d.ts +108 -0
- package/lib/typescript/src/components/StatGroup/StatGroup.d.ts +45 -0
- package/lib/typescript/src/components/StatItem/StatItem.d.ts +24 -7
- package/lib/typescript/src/components/StrengthIndicator/StrengthIndicator.d.ts +58 -0
- package/lib/typescript/src/components/SummaryTile/SummaryTile.d.ts +60 -0
- package/lib/typescript/src/components/index.d.ts +22 -2
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/lib/typescript/src/utils/index.d.ts +1 -0
- package/lib/typescript/src/utils/number-utils.d.ts +29 -0
- package/package.json +1 -1
- package/src/components/AccordionCheckbox/AccordionCheckbox.tsx +323 -0
- package/src/components/BrandChip/BrandChip.tsx +235 -0
- package/src/components/CardBankAccount/CardBankAccount.tsx +295 -0
- package/src/components/CardInsight/CardInsight.tsx +239 -0
- package/src/components/CheckboxGroup/CheckboxGroup.tsx +86 -0
- package/src/components/CheckboxItem/CheckboxItem.tsx +174 -0
- package/src/components/CircularProgressBar/CircularProgressBar.tsx +74 -9
- package/src/components/CoverageBarComparison/CoverageBarComparison.tsx +378 -0
- package/src/components/CoverageRing/CoverageRing.tsx +225 -0
- package/src/components/DonutChart/DonutChart.tsx +503 -0
- package/src/components/DonutChartSummary/DonutChartSummary.tsx +256 -0
- package/src/components/LinearMeter/LinearMeter.tsx +9 -39
- package/src/components/LinearProgress/LinearProgress.tsx +92 -0
- package/src/components/MetricLegendItem/MetricLegendItem.tsx +167 -0
- package/src/components/MonthlyStatusGrid/MonthlyStatusGrid.tsx +438 -0
- package/src/components/OTP/OTP.tsx +476 -29
- package/src/components/ProductOverview/ProductOverview.tsx +236 -0
- package/src/components/RangeTrack/RangeTrack.tsx +394 -0
- package/src/components/SavingsGoalSummary/SavingsGoalSummary.tsx +269 -0
- package/src/components/SegmentedTrack/SegmentedTrack.tsx +268 -0
- package/src/components/StatGroup/StatGroup.tsx +169 -0
- package/src/components/StatItem/StatItem.tsx +117 -40
- package/src/components/StrengthIndicator/StrengthIndicator.tsx +205 -0
- package/src/components/SummaryTile/SummaryTile.tsx +251 -0
- package/src/components/index.ts +32 -2
- package/src/design-tokens/Coin Variables-variables-full.json +1 -1
- package/src/icons/registry.ts +1 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/number-utils.ts +60 -0
|
@@ -3,17 +3,257 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
+
exports.OTPResend = OTPResend;
|
|
6
7
|
exports.default = void 0;
|
|
8
|
+
exports.useOtpResend = useOtpResend;
|
|
7
9
|
var _react = _interopRequireWildcard(require("react"));
|
|
8
10
|
var _reactNative = require("react-native");
|
|
9
11
|
var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
|
|
10
12
|
var _JFSThemeProvider = require("../../design-tokens/JFSThemeProvider");
|
|
11
13
|
var _SupportText = _interopRequireDefault(require("../SupportText/SupportText"));
|
|
14
|
+
var _Button = _interopRequireDefault(require("../Button/Button"));
|
|
12
15
|
var _reactUtils = require("../../utils/react-utils");
|
|
13
16
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
14
17
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
18
|
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); }
|
|
19
|
+
// Default mode overrides for the resend Button. Per design: a small,
|
|
20
|
+
// low-emphasis, neutral-appearance button. Consumers can override any of
|
|
21
|
+
// these via OTPResendConfig.resendButtonModes.
|
|
22
|
+
const DEFAULT_RESEND_BUTTON_MODES = {
|
|
23
|
+
AppearanceBrand: 'Neutral',
|
|
24
|
+
'Button / Size': 'S',
|
|
25
|
+
Emphasis: 'Low'
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// useOtpResend — headless state machine for the resend countdown.
|
|
30
|
+
//
|
|
31
|
+
// State machine: counting -> ready -> sending -> counting -> ...
|
|
32
|
+
//
|
|
33
|
+
// counting : timer is ticking down; resend button disabled.
|
|
34
|
+
// ready : timer elapsed; resend button enabled.
|
|
35
|
+
// sending : an in-flight onResend() promise is pending; button shows
|
|
36
|
+
// a transient "sending" label, prevents double-fire, and
|
|
37
|
+
// restarts the countdown only on resolve.
|
|
38
|
+
//
|
|
39
|
+
// Designed as a hook so consumers can render any UI they want and still
|
|
40
|
+
// reuse the timing/lifecycle logic. The OTPResend component below is the
|
|
41
|
+
// opinionated default.
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Headless hook that drives an OTP resend countdown.
|
|
46
|
+
*
|
|
47
|
+
* The hook is intentionally UI-agnostic: it returns just enough state
|
|
48
|
+
* (`state`, `secondsLeft`, plus `resend()` / `restart()` / `skip()`) so
|
|
49
|
+
* consumers can render their own button, support text, etc. The bundled
|
|
50
|
+
* `OTPResend` component is the canonical UI on top of this hook.
|
|
51
|
+
*/
|
|
52
|
+
function useOtpResend({
|
|
53
|
+
durationSeconds = 30,
|
|
54
|
+
onResend,
|
|
55
|
+
autoStart = true
|
|
56
|
+
} = {}) {
|
|
57
|
+
// Initial state: if we're auto-starting, we begin in 'counting' with
|
|
58
|
+
// the full duration; otherwise we go straight to 'ready' (handy for
|
|
59
|
+
// flows where a previous screen already triggered the SMS and we
|
|
60
|
+
// mount with the timer already elapsed).
|
|
61
|
+
const [state, setState] = (0, _react.useState)(autoStart ? 'counting' : 'ready');
|
|
62
|
+
const [secondsLeft, setSecondsLeft] = (0, _react.useState)(autoStart ? durationSeconds : 0);
|
|
63
|
+
|
|
64
|
+
// Keep a ref to the latest onResend so the resend() callback is stable
|
|
65
|
+
// even if consumers pass an inline arrow function on every render.
|
|
66
|
+
const onResendRef = (0, _react.useRef)(onResend);
|
|
67
|
+
(0, _react.useEffect)(() => {
|
|
68
|
+
onResendRef.current = onResend;
|
|
69
|
+
}, [onResend]);
|
|
70
|
+
const intervalRef = (0, _react.useRef)(null);
|
|
71
|
+
const stopTicker = (0, _react.useCallback)(() => {
|
|
72
|
+
if (intervalRef.current !== null) {
|
|
73
|
+
clearInterval(intervalRef.current);
|
|
74
|
+
intervalRef.current = null;
|
|
75
|
+
}
|
|
76
|
+
}, []);
|
|
77
|
+
const startTicker = (0, _react.useCallback)(seconds => {
|
|
78
|
+
stopTicker();
|
|
79
|
+
const initial = Math.max(0, Math.floor(seconds));
|
|
80
|
+
if (initial === 0) {
|
|
81
|
+
setState('ready');
|
|
82
|
+
setSecondsLeft(0);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
setState('counting');
|
|
86
|
+
setSecondsLeft(initial);
|
|
87
|
+
intervalRef.current = setInterval(() => {
|
|
88
|
+
setSecondsLeft(prev => {
|
|
89
|
+
if (prev <= 1) {
|
|
90
|
+
stopTicker();
|
|
91
|
+
setState('ready');
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
return prev - 1;
|
|
95
|
+
});
|
|
96
|
+
}, 1000);
|
|
97
|
+
}, [stopTicker]);
|
|
98
|
+
|
|
99
|
+
// Auto-start once on mount if requested. We deliberately omit
|
|
100
|
+
// `durationSeconds` from the dependency array so changing the prop
|
|
101
|
+
// mid-flight does not silently reset the countdown — consumers should
|
|
102
|
+
// call `restart()` explicitly when they want a new duration.
|
|
103
|
+
(0, _react.useEffect)(() => {
|
|
104
|
+
if (autoStart) startTicker(durationSeconds);
|
|
105
|
+
return stopTicker;
|
|
106
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
107
|
+
}, []);
|
|
108
|
+
const resend = (0, _react.useCallback)(async () => {
|
|
109
|
+
// Guard against double-fire and out-of-state taps (e.g. an
|
|
110
|
+
// accessibility re-tap while we're already in 'sending').
|
|
111
|
+
if (state !== 'ready') return;
|
|
112
|
+
try {
|
|
113
|
+
setState('sending');
|
|
114
|
+
const result = onResendRef.current?.();
|
|
115
|
+
if (result && typeof result.then === 'function') {
|
|
116
|
+
await result;
|
|
117
|
+
}
|
|
118
|
+
startTicker(durationSeconds);
|
|
119
|
+
} catch (e) {
|
|
120
|
+
// Surface the failure — the consumer can decide whether to
|
|
121
|
+
// show a toast, retry, etc. We move back to 'ready' so the
|
|
122
|
+
// user can try again immediately.
|
|
123
|
+
setState('ready');
|
|
124
|
+
throw e;
|
|
125
|
+
}
|
|
126
|
+
}, [state, durationSeconds, startTicker]);
|
|
127
|
+
const restart = (0, _react.useCallback)(next => {
|
|
128
|
+
startTicker(next ?? durationSeconds);
|
|
129
|
+
}, [durationSeconds, startTicker]);
|
|
130
|
+
const skip = (0, _react.useCallback)(() => {
|
|
131
|
+
stopTicker();
|
|
132
|
+
setState('ready');
|
|
133
|
+
setSecondsLeft(0);
|
|
134
|
+
}, [stopTicker]);
|
|
135
|
+
return {
|
|
136
|
+
state,
|
|
137
|
+
secondsLeft,
|
|
138
|
+
canResend: state === 'ready',
|
|
139
|
+
isSending: state === 'sending',
|
|
140
|
+
resend,
|
|
141
|
+
restart,
|
|
142
|
+
skip
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// OTPResend — opinionated UI built on useOtpResend.
|
|
148
|
+
//
|
|
149
|
+
// Renders a SupportText line that shows "Resend OTP in {n}s" while the
|
|
150
|
+
// countdown is active, and turns into a tappable "Resend" button once the
|
|
151
|
+
// countdown elapses. Re-tapping is debounced via the hook's state machine.
|
|
152
|
+
//
|
|
153
|
+
// Exported as a standalone so consumers can position it anywhere in their
|
|
154
|
+
// layout (it does not need to live inside <OTP />). When passed via
|
|
155
|
+
// <OTP resend={...} />, OTP renders this internally in the support area.
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
const defaultFormatCountdown = s => `Resend OTP in ${s}s`;
|
|
159
|
+
function OTPResend({
|
|
160
|
+
durationSeconds = 30,
|
|
161
|
+
onResend,
|
|
162
|
+
autoStart = true,
|
|
163
|
+
formatCountdown,
|
|
164
|
+
sendingLabel = 'Sending…',
|
|
165
|
+
resendLabel = 'Resend',
|
|
166
|
+
countdownStatus = 'Loading',
|
|
167
|
+
resendButtonModes,
|
|
168
|
+
modes: propModes = _reactUtils.EMPTY_MODES,
|
|
169
|
+
style
|
|
170
|
+
}) {
|
|
171
|
+
const {
|
|
172
|
+
modes: globalModes
|
|
173
|
+
} = (0, _JFSThemeProvider.useTokens)();
|
|
174
|
+
const modes = (0, _react.useMemo)(() => ({
|
|
175
|
+
...globalModes,
|
|
176
|
+
...propModes
|
|
177
|
+
}), [globalModes, propModes]);
|
|
178
|
+
|
|
179
|
+
// The Button gets the consumer's modes layered first, then the
|
|
180
|
+
// component-default brand/size/emphasis trio, and finally any caller
|
|
181
|
+
// overrides via `resendButtonModes`. Spreading in this order lets
|
|
182
|
+
// consumers (a) keep theming like Color Mode propagating through,
|
|
183
|
+
// (b) rely on the small/low/neutral defaults out of the box, and
|
|
184
|
+
// (c) selectively override e.g. `Button / Size: 'M'` without losing
|
|
185
|
+
// the other defaults.
|
|
186
|
+
const resolvedButtonModes = (0, _react.useMemo)(() => ({
|
|
187
|
+
...modes,
|
|
188
|
+
...DEFAULT_RESEND_BUTTON_MODES,
|
|
189
|
+
...resendButtonModes
|
|
190
|
+
}), [modes, resendButtonModes]);
|
|
191
|
+
const {
|
|
192
|
+
state,
|
|
193
|
+
secondsLeft,
|
|
194
|
+
canResend,
|
|
195
|
+
resend
|
|
196
|
+
} = useOtpResend({
|
|
197
|
+
durationSeconds,
|
|
198
|
+
onResend,
|
|
199
|
+
autoStart
|
|
200
|
+
});
|
|
201
|
+
const formatter = formatCountdown ?? defaultFormatCountdown;
|
|
202
|
+
|
|
203
|
+
// counting → static SupportText. Not a Pressable: tapping the
|
|
204
|
+
// countdown should not do anything (it's an inert status line).
|
|
205
|
+
if (state === 'counting') {
|
|
206
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_SupportText.default, {
|
|
207
|
+
label: formatter(secondsLeft),
|
|
208
|
+
status: countdownStatus,
|
|
209
|
+
modes: modes,
|
|
210
|
+
style: style
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// sending → static SupportText with a transient "Sending…" label so
|
|
215
|
+
// the user has clear feedback that their tap was received and we
|
|
216
|
+
// don't accept another tap until the resend completes.
|
|
217
|
+
if (state === 'sending') {
|
|
218
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_SupportText.default, {
|
|
219
|
+
label: sendingLabel,
|
|
220
|
+
status: countdownStatus,
|
|
221
|
+
modes: modes,
|
|
222
|
+
style: style
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ready → render a real Button (no leading/trailing icon — design
|
|
227
|
+
// calls for a clean text-only pill once the countdown elapses). The
|
|
228
|
+
// Button handles its own pressed/hover states from tokens, so we
|
|
229
|
+
// don't need to layer extra opacity etc. on top.
|
|
230
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Button.default, {
|
|
231
|
+
label: resendLabel,
|
|
232
|
+
modes: resolvedButtonModes,
|
|
233
|
+
disabled: !canResend,
|
|
234
|
+
onPress: () => {
|
|
235
|
+
// Swallow rejections here — the hook re-throws so callers
|
|
236
|
+
// wiring useOtpResend directly can react, but at the
|
|
237
|
+
// component boundary we never want an unhandled promise.
|
|
238
|
+
resend().catch(() => {});
|
|
239
|
+
},
|
|
240
|
+
accessibilityLabel: resendLabel,
|
|
241
|
+
style: style
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
// OTP
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
|
|
16
249
|
const DIGITS_ONLY = /^\d*$/;
|
|
250
|
+
|
|
251
|
+
// How long the underline takes to fade in when a slot becomes
|
|
252
|
+
// "highlighted" (filled or actively focused), and to fade out when a slot
|
|
253
|
+
// reverts to idle (e.g. on backspace). The asymmetric durations make the
|
|
254
|
+
// "fades back" cue intentional without being sluggish.
|
|
255
|
+
const SLOT_FADE_IN_MS = 120;
|
|
256
|
+
const SLOT_FADE_OUT_MS = 220;
|
|
17
257
|
function OTP({
|
|
18
258
|
length = 6,
|
|
19
259
|
value: controlledValue,
|
|
@@ -28,20 +268,25 @@ function OTP({
|
|
|
28
268
|
modes: propModes = _reactUtils.EMPTY_MODES,
|
|
29
269
|
style,
|
|
30
270
|
supportText,
|
|
31
|
-
supportTextStatus
|
|
271
|
+
supportTextStatus,
|
|
272
|
+
errorMessage,
|
|
273
|
+
resend,
|
|
274
|
+
enableSmsAutofill = true
|
|
32
275
|
}) {
|
|
33
276
|
const {
|
|
34
277
|
modes: globalModes
|
|
35
278
|
} = (0, _JFSThemeProvider.useTokens)();
|
|
36
|
-
const modes = {
|
|
279
|
+
const modes = (0, _react.useMemo)(() => ({
|
|
37
280
|
...globalModes,
|
|
38
281
|
...propModes
|
|
39
|
-
};
|
|
282
|
+
}), [globalModes, propModes]);
|
|
40
283
|
const isControlled = controlledValue !== undefined;
|
|
41
284
|
const [internalValue, setInternalValue] = (0, _react.useState)(defaultValue);
|
|
42
285
|
const currentValue = isControlled ? controlledValue : internalValue;
|
|
43
286
|
const inputRef = (0, _react.useRef)(null);
|
|
44
287
|
const [isFocused, setIsFocused] = (0, _react.useState)(false);
|
|
288
|
+
|
|
289
|
+
// --- Caret blink (unchanged) ---
|
|
45
290
|
const caretAnim = (0, _react.useRef)(new _reactNative.Animated.Value(1)).current;
|
|
46
291
|
(0, _react.useEffect)(() => {
|
|
47
292
|
if (!isFocused) return;
|
|
@@ -87,7 +332,6 @@ function OTP({
|
|
|
87
332
|
const otpPaddingV = Number((0, _figmaVariablesResolver.getVariableByName)('otp/padding/vertical', modes)) || 8;
|
|
88
333
|
const slotWidth = Number((0, _figmaVariablesResolver.getVariableByName)('pinSlot/width', modes)) || 48;
|
|
89
334
|
const slotGap = Number((0, _figmaVariablesResolver.getVariableByName)('pinSlot/gap', modes)) || 8;
|
|
90
|
-
// digit/color has no state variants in Figma — resolved once from the Output collection
|
|
91
335
|
const digitColor = (0, _figmaVariablesResolver.getVariableByName)('pinSlot/digit/color', modes) || '#000000';
|
|
92
336
|
const digitFontSize = Number((0, _figmaVariablesResolver.getVariableByName)('pinSlot/digit/fontSize', modes)) || 24;
|
|
93
337
|
const digitFontFamily = (0, _figmaVariablesResolver.getVariableByName)('pinSlot/digit/fontFamily', modes) || 'JioType Var';
|
|
@@ -96,25 +340,68 @@ function OTP({
|
|
|
96
340
|
const underlineHeight = Number((0, _figmaVariablesResolver.getVariableByName)('pinSlot/underline/height', modes)) || 2;
|
|
97
341
|
const underlineRadius = Number((0, _figmaVariablesResolver.getVariableByName)('pinSlot/underline/radius', modes)) || 1;
|
|
98
342
|
|
|
99
|
-
// ---
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
343
|
+
// --- Resolve the three underline colors ONCE per render. We then
|
|
344
|
+
// animate between idle ↔ active per-slot using interpolation; the
|
|
345
|
+
// error color is applied directly (no animation) when isInvalid.
|
|
346
|
+
const idleUnderlineColor = (0, _react.useMemo)(() => (0, _figmaVariablesResolver.getVariableByName)('PinSlot/underline/color', {
|
|
347
|
+
...modes,
|
|
348
|
+
'Input/PINSlot States': 'Idle'
|
|
349
|
+
}) || '#303338', [modes]);
|
|
350
|
+
const activeUnderlineColor = (0, _react.useMemo)(() => (0, _figmaVariablesResolver.getVariableByName)('PinSlot/underline/color', {
|
|
351
|
+
...modes,
|
|
352
|
+
'Input/PINSlot States': 'Active'
|
|
353
|
+
}) || '#5d00b5', [modes]);
|
|
354
|
+
const errorUnderlineColor = (0, _react.useMemo)(() => (0, _figmaVariablesResolver.getVariableByName)('PinSlot/underline/color', {
|
|
355
|
+
...modes,
|
|
356
|
+
'Input/PINSlot States': 'Error'
|
|
357
|
+
}) || '#d92d20', [modes]);
|
|
358
|
+
|
|
359
|
+
// --- Per-slot underline highlight animations ---
|
|
360
|
+
//
|
|
361
|
+
// Each slot owns one Animated.Value in [0, 1]. 0 = Idle color, 1 =
|
|
362
|
+
// Active color. We re-target on every value/focus change:
|
|
363
|
+
//
|
|
364
|
+
// highlighted = isFilled || (isActiveSlot && isFocused)
|
|
365
|
+
//
|
|
366
|
+
// Filled slots stay lit, so adding a digit instantly recruits its
|
|
367
|
+
// slot into the lit cohort. Deleting a digit transitions that slot
|
|
368
|
+
// (which is no longer the active slot — the cursor moved back) from
|
|
369
|
+
// 1 → 0, producing the "fades back" cue the design calls for.
|
|
370
|
+
//
|
|
371
|
+
// We use useNativeDriver:false because backgroundColor cannot run on
|
|
372
|
+
// the native driver (it's a JS-thread layout property). The overhead
|
|
373
|
+
// is fine here: at most `length` (≤ ~8) values transitioning briefly
|
|
374
|
+
// on each keystroke.
|
|
375
|
+
const slotAnimsRef = (0, _react.useRef)([]);
|
|
376
|
+
if (slotAnimsRef.current.length !== length) {
|
|
377
|
+
const next = [];
|
|
378
|
+
for (let i = 0; i < length; i++) {
|
|
379
|
+
const existing = slotAnimsRef.current[i];
|
|
380
|
+
// Initialize fresh slots to match their *current* highlight
|
|
381
|
+
// target. This avoids a flash on first mount when consumers
|
|
382
|
+
// pass a non-empty defaultValue (slots would otherwise fade
|
|
383
|
+
// in from 0 even though they're already filled).
|
|
384
|
+
const initial = i < currentValue.length ? 1 : 0;
|
|
385
|
+
next.push(existing ?? new _reactNative.Animated.Value(initial));
|
|
386
|
+
}
|
|
387
|
+
slotAnimsRef.current = next;
|
|
388
|
+
}
|
|
389
|
+
(0, _react.useEffect)(() => {
|
|
390
|
+
const anims = slotAnimsRef.current;
|
|
391
|
+
const filledLen = currentValue.length;
|
|
392
|
+
for (let i = 0; i < length; i++) {
|
|
393
|
+
const slotAnim = anims[i];
|
|
394
|
+
if (!slotAnim) continue;
|
|
395
|
+
const isFilled = i < filledLen;
|
|
396
|
+
const isActiveSlot = i === filledLen && filledLen < length && isFocused;
|
|
397
|
+
const target = isFilled || isActiveSlot ? 1 : 0;
|
|
398
|
+
_reactNative.Animated.timing(slotAnim, {
|
|
399
|
+
toValue: target,
|
|
400
|
+
duration: target === 1 ? SLOT_FADE_IN_MS : SLOT_FADE_OUT_MS,
|
|
401
|
+
useNativeDriver: false
|
|
402
|
+
}).start();
|
|
403
|
+
}
|
|
404
|
+
}, [currentValue, isFocused, length]);
|
|
118
405
|
|
|
119
406
|
// --- Styles ---
|
|
120
407
|
const containerStyle = {
|
|
@@ -134,12 +421,6 @@ function OTP({
|
|
|
134
421
|
const char = currentValue[index];
|
|
135
422
|
const isActiveSlot = index === currentValue.length && currentValue.length < length;
|
|
136
423
|
const isFilled = char !== undefined;
|
|
137
|
-
|
|
138
|
-
// Underline color is the only state-sensitive token (lives in "Input/PINSlot States" collection).
|
|
139
|
-
// Note: token name is "PinSlot/underline/color" (capital P/S) — different from the static
|
|
140
|
-
// "pinSlot/underline/color" in the Output collection.
|
|
141
|
-
const slotModes = getSlotModes(isActiveSlot);
|
|
142
|
-
const underlineColor = (0, _figmaVariablesResolver.getVariableByName)('PinSlot/underline/color', slotModes) || '#303338';
|
|
143
424
|
const slotStyle = {
|
|
144
425
|
width: slotWidth,
|
|
145
426
|
flexDirection: 'column',
|
|
@@ -156,11 +437,22 @@ function OTP({
|
|
|
156
437
|
textAlign: 'center',
|
|
157
438
|
minWidth: '100%'
|
|
158
439
|
};
|
|
440
|
+
|
|
441
|
+
// Pull the per-slot animated value (always exists by this point —
|
|
442
|
+
// we resync the ref array above before render).
|
|
443
|
+
const slotAnim = slotAnimsRef.current[index] ?? new _reactNative.Animated.Value(0);
|
|
444
|
+
const interpolatedColor = slotAnim.interpolate({
|
|
445
|
+
inputRange: [0, 1],
|
|
446
|
+
outputRange: [idleUnderlineColor, activeUnderlineColor]
|
|
447
|
+
});
|
|
159
448
|
const underlineStyle = {
|
|
160
449
|
width: slotWidth,
|
|
161
450
|
height: underlineHeight,
|
|
162
451
|
borderRadius: underlineRadius,
|
|
163
|
-
|
|
452
|
+
// Error state takes precedence and snaps directly to the
|
|
453
|
+
// error color — animating *to* an error feels less urgent
|
|
454
|
+
// than the snap, and the design uses an instant transition.
|
|
455
|
+
backgroundColor: isInvalid ? errorUnderlineColor : interpolatedColor
|
|
164
456
|
};
|
|
165
457
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
166
458
|
style: slotStyle,
|
|
@@ -187,24 +479,67 @@ function OTP({
|
|
|
187
479
|
}],
|
|
188
480
|
children: '\u00A0'
|
|
189
481
|
})
|
|
190
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
482
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
|
|
191
483
|
style: underlineStyle
|
|
192
484
|
})]
|
|
193
485
|
}, index);
|
|
194
486
|
};
|
|
195
|
-
|
|
487
|
+
|
|
488
|
+
// --- Support area rendering ---
|
|
489
|
+
//
|
|
490
|
+
// Priority:
|
|
491
|
+
// 1. isInvalid → errorMessage (with Error status). Falls back to
|
|
492
|
+
// `supportText` if errorMessage isn't provided, promoting its
|
|
493
|
+
// status to Error.
|
|
494
|
+
// 2. resend (and !isInvalid) → managed countdown / button.
|
|
495
|
+
// 3. supportText → user's static support text.
|
|
496
|
+
// 4. nothing.
|
|
497
|
+
//
|
|
498
|
+
// This split keeps validation a parent concern (the component never
|
|
499
|
+
// tries to "know" what valid means) while still giving consumers a
|
|
500
|
+
// turnkey error UI when they flip `isInvalid`.
|
|
501
|
+
//
|
|
502
|
+
// While `isInvalid` is true we also inject `Status: 'Error'` into the
|
|
503
|
+
// mode set forwarded to the support area. This lets the SupportText
|
|
504
|
+
// (and any nested icon) resolve error-themed Figma tokens — foreground
|
|
505
|
+
// color, icon color, etc. — without consumers having to thread the
|
|
506
|
+
// collection mode in by hand.
|
|
507
|
+
const supportModes = (0, _react.useMemo)(() => isInvalid ? {
|
|
508
|
+
...modes,
|
|
509
|
+
Status: 'Error'
|
|
510
|
+
} : modes, [modes, isInvalid]);
|
|
511
|
+
const renderStaticSupportText = overrideStatus => {
|
|
196
512
|
if (!supportText) return null;
|
|
197
513
|
if (typeof supportText === 'string') {
|
|
198
514
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_SupportText.default, {
|
|
199
515
|
label: supportText,
|
|
200
|
-
status:
|
|
201
|
-
modes:
|
|
516
|
+
status: overrideStatus ?? supportTextStatus ?? 'Neutral',
|
|
517
|
+
modes: supportModes
|
|
202
518
|
});
|
|
203
519
|
}
|
|
204
520
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
|
|
205
|
-
children: (0, _reactUtils.cloneChildrenWithModes)(_react.default.Children.toArray(supportText),
|
|
521
|
+
children: (0, _reactUtils.cloneChildrenWithModes)(_react.default.Children.toArray(supportText), supportModes)
|
|
206
522
|
});
|
|
207
523
|
};
|
|
524
|
+
const renderSupportArea = () => {
|
|
525
|
+
if (isInvalid) {
|
|
526
|
+
if (errorMessage) {
|
|
527
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_SupportText.default, {
|
|
528
|
+
label: errorMessage,
|
|
529
|
+
status: "Error",
|
|
530
|
+
modes: supportModes
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
return renderStaticSupportText('Error');
|
|
534
|
+
}
|
|
535
|
+
if (resend) {
|
|
536
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(OTPResend, {
|
|
537
|
+
...resend,
|
|
538
|
+
modes: supportModes
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
return renderStaticSupportText();
|
|
542
|
+
};
|
|
208
543
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
|
|
209
544
|
style: [containerStyle, isDisabled && {
|
|
210
545
|
opacity: 0.4
|
|
@@ -222,7 +557,16 @@ function OTP({
|
|
|
222
557
|
editable: !isDisabled,
|
|
223
558
|
onFocus: () => setIsFocused(true),
|
|
224
559
|
onBlur: () => setIsFocused(false),
|
|
225
|
-
caretHidden: true
|
|
560
|
+
caretHidden: true
|
|
561
|
+
// Cross-platform native one-time-code autofill. iOS reads
|
|
562
|
+
// `textContentType="oneTimeCode"` to surface SMS codes in
|
|
563
|
+
// the QuickType bar (no library needed). Android reads
|
|
564
|
+
// `autoComplete="one-time-code"` (the canonical RN value;
|
|
565
|
+
// also accepted as a hint by the SMS Retriever / SMS User
|
|
566
|
+
// Consent APIs that the host app wires up natively).
|
|
567
|
+
,
|
|
568
|
+
textContentType: enableSmsAutofill ? 'oneTimeCode' : 'none',
|
|
569
|
+
autoComplete: enableSmsAutofill ? 'one-time-code' : 'off',
|
|
226
570
|
style: {
|
|
227
571
|
position: 'absolute',
|
|
228
572
|
width: 1,
|
|
@@ -236,7 +580,7 @@ function OTP({
|
|
|
236
580
|
children: Array.from({
|
|
237
581
|
length
|
|
238
582
|
}, (_, i) => renderSlot(i))
|
|
239
|
-
}),
|
|
583
|
+
}), renderSupportArea()]
|
|
240
584
|
});
|
|
241
585
|
}
|
|
242
586
|
var _default = exports.default = OTP;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _react = _interopRequireDefault(require("react"));
|
|
8
|
+
var _reactNative = require("react-native");
|
|
9
|
+
var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
|
|
10
|
+
var _reactUtils = require("../../utils/react-utils");
|
|
11
|
+
var _Image = _interopRequireDefault(require("../Image/Image"));
|
|
12
|
+
var _ProductLabel = _interopRequireDefault(require("../ProductLabel/ProductLabel"));
|
|
13
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
14
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
|
+
const DEFAULT_STATS = [{
|
|
16
|
+
value: '995',
|
|
17
|
+
label: 'Purity'
|
|
18
|
+
}, {
|
|
19
|
+
value: '3%',
|
|
20
|
+
label: 'GST'
|
|
21
|
+
}];
|
|
22
|
+
const ProductOverview = ({
|
|
23
|
+
imageSource,
|
|
24
|
+
imageRatio = 288 / 170,
|
|
25
|
+
labelImageSource,
|
|
26
|
+
label = 'Gold',
|
|
27
|
+
productName = '0.5g Gold Coin',
|
|
28
|
+
description = 'Your gold is insured from our vault to you. If lost or damaged, we’ll replace it.',
|
|
29
|
+
stats = DEFAULT_STATS,
|
|
30
|
+
modes = _reactUtils.EMPTY_MODES,
|
|
31
|
+
style,
|
|
32
|
+
children
|
|
33
|
+
}) => {
|
|
34
|
+
const padding = (0, _figmaVariablesResolver.getVariableByName)('productOverview/padding', modes) ?? 24;
|
|
35
|
+
const gap = (0, _figmaVariablesResolver.getVariableByName)('productOverview/gap', modes) ?? 12;
|
|
36
|
+
const background = (0, _figmaVariablesResolver.getVariableByName)('productOverview/background', modes) ?? '#ffffff';
|
|
37
|
+
const productNameColor = (0, _figmaVariablesResolver.getVariableByName)('productOverview/productName/color', modes) ?? '#0d0d0f';
|
|
38
|
+
const productNameFontFamily = (0, _figmaVariablesResolver.getVariableByName)('productOverview/productName/fontFamily', modes) ?? 'JioType Var';
|
|
39
|
+
const productNameFontSize = (0, _figmaVariablesResolver.getVariableByName)('productOverview/productName/fontSize', modes) ?? 26;
|
|
40
|
+
const productNameFontWeight = (0, _figmaVariablesResolver.getVariableByName)('productOverview/productName/fontWeight', modes) ?? 900;
|
|
41
|
+
const productNameLineHeight = (0, _figmaVariablesResolver.getVariableByName)('productOverview/productName/lineHeight', modes) ?? 26;
|
|
42
|
+
const descriptionColor = (0, _figmaVariablesResolver.getVariableByName)('productOverview/description/color', modes) ?? '#1a1c1f';
|
|
43
|
+
const descriptionFontFamily = (0, _figmaVariablesResolver.getVariableByName)('productOverview/description/fontFamily', modes) ?? 'JioType Var';
|
|
44
|
+
const descriptionFontSize = (0, _figmaVariablesResolver.getVariableByName)('productOverview/description/fontSize', modes) ?? 14;
|
|
45
|
+
const descriptionFontWeight = (0, _figmaVariablesResolver.getVariableByName)('productOverview/description/fontWeight', modes) ?? 500;
|
|
46
|
+
const descriptionLineHeight = (0, _figmaVariablesResolver.getVariableByName)('productOverview/description/lineHeight', modes) ?? 18.2;
|
|
47
|
+
const statGap = (0, _figmaVariablesResolver.getVariableByName)('productOverview/stat/gap', modes) ?? 2;
|
|
48
|
+
const statValueColor = (0, _figmaVariablesResolver.getVariableByName)('productOverview/stat/value/color', modes) ?? '#141414';
|
|
49
|
+
const statValueFontFamily = (0, _figmaVariablesResolver.getVariableByName)('productOverview/stat/value/fontFamily', modes) ?? 'JioType Var';
|
|
50
|
+
const statValueFontSize = (0, _figmaVariablesResolver.getVariableByName)('productOverview/stat/value/fontSize', modes) ?? 20;
|
|
51
|
+
const statValueFontWeight = (0, _figmaVariablesResolver.getVariableByName)('productOverview/stat/value/fontWeight', modes) ?? 900;
|
|
52
|
+
const statValueLineHeight = (0, _figmaVariablesResolver.getVariableByName)('productOverview/stat/value/lineHeight', modes) ?? 20;
|
|
53
|
+
const statLabelColor = productNameColor;
|
|
54
|
+
const statLabelFontFamily = (0, _figmaVariablesResolver.getVariableByName)('productOverview/stat/label/fontFamily', modes) ?? 'JioType Var';
|
|
55
|
+
const statLabelFontSize = (0, _figmaVariablesResolver.getVariableByName)('productOverview/stat/label/fontSize', modes) ?? 12;
|
|
56
|
+
const statLabelFontWeight = (0, _figmaVariablesResolver.getVariableByName)('productOverview/stat/label/fontWeight', modes) ?? 400;
|
|
57
|
+
const statLabelLineHeight = (0, _figmaVariablesResolver.getVariableByName)('productOverview/stat/label/lineHeight', modes) ?? 15.6;
|
|
58
|
+
const productNameStyle = {
|
|
59
|
+
color: productNameColor,
|
|
60
|
+
fontFamily: productNameFontFamily,
|
|
61
|
+
fontSize: productNameFontSize,
|
|
62
|
+
fontWeight: String(productNameFontWeight),
|
|
63
|
+
lineHeight: productNameLineHeight,
|
|
64
|
+
textAlign: 'center'
|
|
65
|
+
};
|
|
66
|
+
const descriptionStyle = {
|
|
67
|
+
color: descriptionColor,
|
|
68
|
+
fontFamily: descriptionFontFamily,
|
|
69
|
+
fontSize: descriptionFontSize,
|
|
70
|
+
fontWeight: String(descriptionFontWeight),
|
|
71
|
+
lineHeight: descriptionLineHeight,
|
|
72
|
+
textAlign: 'center'
|
|
73
|
+
};
|
|
74
|
+
const statValueStyle = {
|
|
75
|
+
color: statValueColor,
|
|
76
|
+
fontFamily: statValueFontFamily,
|
|
77
|
+
fontSize: statValueFontSize,
|
|
78
|
+
fontWeight: String(statValueFontWeight),
|
|
79
|
+
lineHeight: statValueLineHeight
|
|
80
|
+
};
|
|
81
|
+
const statLabelStyle = {
|
|
82
|
+
color: statLabelColor,
|
|
83
|
+
fontFamily: statLabelFontFamily,
|
|
84
|
+
fontSize: statLabelFontSize,
|
|
85
|
+
fontWeight: String(statLabelFontWeight),
|
|
86
|
+
lineHeight: statLabelLineHeight,
|
|
87
|
+
textAlign: 'center'
|
|
88
|
+
};
|
|
89
|
+
const showStats = Array.isArray(stats) && stats.length > 0;
|
|
90
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
91
|
+
style: [{
|
|
92
|
+
backgroundColor: background,
|
|
93
|
+
padding,
|
|
94
|
+
gap,
|
|
95
|
+
alignItems: 'center',
|
|
96
|
+
width: '100%'
|
|
97
|
+
}, style],
|
|
98
|
+
children: [imageSource != null && /*#__PURE__*/(0, _jsxRuntime.jsx)(_Image.default, {
|
|
99
|
+
imageSource: imageSource,
|
|
100
|
+
ratio: imageRatio,
|
|
101
|
+
resizeMode: "contain",
|
|
102
|
+
accessibilityElementsHidden: true,
|
|
103
|
+
importantForAccessibility: "no"
|
|
104
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_ProductLabel.default, {
|
|
105
|
+
label: label,
|
|
106
|
+
...(labelImageSource != null && {
|
|
107
|
+
imageSource: labelImageSource
|
|
108
|
+
}),
|
|
109
|
+
modes: modes
|
|
110
|
+
}), productName ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
111
|
+
style: productNameStyle,
|
|
112
|
+
accessibilityRole: "header",
|
|
113
|
+
children: productName
|
|
114
|
+
}) : null, description ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
115
|
+
style: descriptionStyle,
|
|
116
|
+
children: description
|
|
117
|
+
}) : null, children ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
|
|
118
|
+
children: (0, _reactUtils.cloneChildrenWithModes)(children, modes)
|
|
119
|
+
}) : null, showStats && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
120
|
+
style: {
|
|
121
|
+
flexDirection: 'row',
|
|
122
|
+
alignItems: 'center',
|
|
123
|
+
justifyContent: 'space-between',
|
|
124
|
+
width: '100%'
|
|
125
|
+
},
|
|
126
|
+
children: stats.map((stat, index) => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
127
|
+
style: {
|
|
128
|
+
flex: 1,
|
|
129
|
+
minWidth: 0,
|
|
130
|
+
alignItems: 'center',
|
|
131
|
+
gap: statGap,
|
|
132
|
+
overflow: 'hidden'
|
|
133
|
+
},
|
|
134
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
135
|
+
style: statValueStyle,
|
|
136
|
+
numberOfLines: 1,
|
|
137
|
+
children: stat.value
|
|
138
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
139
|
+
style: statLabelStyle,
|
|
140
|
+
numberOfLines: 1,
|
|
141
|
+
children: stat.label
|
|
142
|
+
})]
|
|
143
|
+
}, `${stat.label}-${index}`))
|
|
144
|
+
})]
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
var _default = exports.default = ProductOverview;
|