jfs-components 0.1.2 → 0.1.8
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 +29 -0
- package/lib/commonjs/components/AmountInput/AmountInput.js +8 -5
- package/lib/commonjs/components/BenefitCard/BenefitCard.js +231 -0
- package/lib/commonjs/components/CcCard/CcCard.js +470 -0
- package/lib/commonjs/components/Checkbox/Checkbox.js +4 -3
- package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +4 -3
- package/lib/commonjs/components/CompareTable/CompareTable.js +372 -0
- package/lib/commonjs/components/ComparisonBar/ComparisonBar.js +266 -0
- package/lib/commonjs/components/DropdownInput/DropdownInput.js +35 -3
- package/lib/commonjs/components/FormField/FormField.js +4 -3
- package/lib/commonjs/components/InputSearch/InputSearch.js +6 -4
- package/lib/commonjs/components/NoteInput/NoteInput.js +6 -5
- package/lib/commonjs/components/PdpCcCard/PdpCcCard.js +273 -0
- package/lib/commonjs/components/ProductMerchandisingCard/GlassFill.js +263 -0
- package/lib/commonjs/components/ProductMerchandisingCard/GlassFill.web.js +116 -0
- package/lib/commonjs/components/ProductMerchandisingCard/ProductMerchandisingCard.js +353 -0
- package/lib/commonjs/components/ProjectionMarker/ProjectionMarker.js +161 -0
- package/lib/commonjs/components/Radio/Radio.js +5 -5
- package/lib/commonjs/components/Slider/Slider.js +473 -0
- package/lib/commonjs/components/TextInput/TextInput.js +13 -8
- package/lib/commonjs/components/TextSegment/TextSegment.js +118 -0
- package/lib/commonjs/components/index.js +63 -0
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/commonjs/design-tokens/figma-modes.generated.js +38 -9
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/commonjs/utils/react-utils.js +22 -0
- package/lib/module/components/AmountInput/AmountInput.js +6 -4
- package/lib/module/components/BenefitCard/BenefitCard.js +225 -0
- package/lib/module/components/CcCard/CcCard.js +464 -0
- package/lib/module/components/Checkbox/Checkbox.js +5 -4
- package/lib/module/components/CheckboxItem/CheckboxItem.js +5 -4
- package/lib/module/components/CompareTable/CompareTable.js +367 -0
- package/lib/module/components/ComparisonBar/ComparisonBar.js +260 -0
- package/lib/module/components/DropdownInput/DropdownInput.js +36 -4
- package/lib/module/components/FormField/FormField.js +5 -4
- package/lib/module/components/InputSearch/InputSearch.js +6 -4
- package/lib/module/components/NoteInput/NoteInput.js +7 -6
- package/lib/module/components/PdpCcCard/PdpCcCard.js +267 -0
- package/lib/module/components/ProductMerchandisingCard/GlassFill.js +257 -0
- package/lib/module/components/ProductMerchandisingCard/GlassFill.web.js +111 -0
- package/lib/module/components/ProductMerchandisingCard/ProductMerchandisingCard.js +347 -0
- package/lib/module/components/ProjectionMarker/ProjectionMarker.js +156 -0
- package/lib/module/components/Radio/Radio.js +5 -4
- package/lib/module/components/Slider/Slider.js +468 -0
- package/lib/module/components/TextInput/TextInput.js +15 -10
- package/lib/module/components/TextSegment/TextSegment.js +113 -0
- package/lib/module/components/index.js +9 -0
- package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/module/design-tokens/figma-modes.generated.js +38 -9
- package/lib/module/icons/registry.js +1 -1
- package/lib/module/utils/react-utils.js +21 -0
- package/lib/typescript/src/components/AmountInput/AmountInput.d.ts +3 -2
- package/lib/typescript/src/components/BenefitCard/BenefitCard.d.ts +93 -0
- package/lib/typescript/src/components/CcCard/CcCard.d.ts +137 -0
- package/lib/typescript/src/components/Checkbox/Checkbox.d.ts +3 -2
- package/lib/typescript/src/components/CheckboxItem/CheckboxItem.d.ts +2 -2
- package/lib/typescript/src/components/CompareTable/CompareTable.d.ts +88 -0
- package/lib/typescript/src/components/ComparisonBar/ComparisonBar.d.ts +118 -0
- package/lib/typescript/src/components/DropdownInput/DropdownInput.d.ts +20 -1
- package/lib/typescript/src/components/FormField/FormField.d.ts +2 -2
- package/lib/typescript/src/components/InputSearch/InputSearch.d.ts +23 -2
- package/lib/typescript/src/components/NoteInput/NoteInput.d.ts +19 -2
- package/lib/typescript/src/components/PdpCcCard/PdpCcCard.d.ts +84 -0
- package/lib/typescript/src/components/ProductMerchandisingCard/GlassFill.d.ts +56 -0
- package/lib/typescript/src/components/ProductMerchandisingCard/GlassFill.web.d.ts +27 -0
- package/lib/typescript/src/components/ProductMerchandisingCard/ProductMerchandisingCard.d.ts +81 -0
- package/lib/typescript/src/components/ProjectionMarker/ProjectionMarker.d.ts +82 -0
- package/lib/typescript/src/components/Radio/Radio.d.ts +3 -2
- package/lib/typescript/src/components/RadioButton/RadioButton.d.ts +2 -2
- package/lib/typescript/src/components/Slider/Slider.d.ts +99 -0
- package/lib/typescript/src/components/TextInput/TextInput.d.ts +9 -29
- package/lib/typescript/src/components/TextSegment/TextSegment.d.ts +100 -0
- package/lib/typescript/src/components/index.d.ts +10 -1
- package/lib/typescript/src/design-tokens/figma-modes.generated.d.ts +22 -2
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/lib/typescript/src/utils/react-utils.d.ts +10 -0
- package/package.json +2 -1
- package/src/components/AmountInput/AmountInput.tsx +7 -5
- package/src/components/BenefitCard/BenefitCard.tsx +309 -0
- package/src/components/CcCard/CcCard.tsx +598 -0
- package/src/components/Checkbox/Checkbox.tsx +5 -4
- package/src/components/CheckboxItem/CheckboxItem.tsx +5 -4
- package/src/components/CompareTable/CompareTable.tsx +477 -0
- package/src/components/ComparisonBar/ComparisonBar.tsx +356 -0
- package/src/components/DropdownInput/DropdownInput.tsx +55 -3
- package/src/components/FormField/FormField.tsx +5 -4
- package/src/components/InputSearch/InputSearch.tsx +8 -5
- package/src/components/NoteInput/NoteInput.tsx +8 -6
- package/src/components/PdpCcCard/PdpCcCard.tsx +356 -0
- package/src/components/ProductMerchandisingCard/GlassFill.tsx +276 -0
- package/src/components/ProductMerchandisingCard/GlassFill.web.tsx +127 -0
- package/src/components/ProductMerchandisingCard/ProductMerchandisingCard.tsx +423 -0
- package/src/components/ProjectionMarker/ProjectionMarker.tsx +277 -0
- package/src/components/Radio/Radio.tsx +5 -4
- package/src/components/Slider/Slider.tsx +628 -0
- package/src/components/TextInput/TextInput.tsx +15 -11
- package/src/components/TextSegment/TextSegment.tsx +166 -0
- package/src/components/index.ts +10 -1
- package/src/design-tokens/Coin Variables-variables-full.json +1 -1
- package/src/design-tokens/figma-modes.generated.ts +38 -9
- package/src/icons/registry.ts +1 -1
- package/src/utils/react-utils.ts +23 -0
- package/lib/typescript/scripts/extract-component-tokens.d.ts +0 -9
- package/lib/typescript/scripts/generate-component-docs.d.ts +0 -9
- package/lib/typescript/scripts/generate-icon-registry.d.ts +0 -3
- package/lib/typescript/scripts/generate-mode-types.d.ts +0 -2
- package/lib/typescript/scripts/retype-modes.d.cts +0 -2
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React, { forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { View, Text, PanResponder, Platform } from 'react-native';
|
|
5
|
+
import Svg, { Path } from 'react-native-svg';
|
|
6
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
7
|
+
import { EMPTY_MODES } from '../../utils/react-utils';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Imperative handle exposed via `ref`. The primary way to read a slider's value
|
|
11
|
+
* is still the controlled `value` + `onChange` pair (each `<Slider />` reports
|
|
12
|
+
* its own value, so multiple sliders are disambiguated by their own handlers).
|
|
13
|
+
* The ref is a convenience for reading the latest value on demand (e.g. on a
|
|
14
|
+
* button press) or imperatively nudging an uncontrolled slider.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const slider = useRef<SliderHandle>(null)
|
|
18
|
+
* slider.current?.setValue(50)
|
|
19
|
+
* const v = slider.current?.getValue()
|
|
20
|
+
*/
|
|
21
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
22
|
+
const clamp = (n, min, max) => Math.min(Math.max(n, min), max);
|
|
23
|
+
const asNum = (raw, fallback) => {
|
|
24
|
+
const n = typeof raw === 'number' ? raw : parseFloat(raw);
|
|
25
|
+
return Number.isFinite(n) ? n : fallback;
|
|
26
|
+
};
|
|
27
|
+
const asStr = (raw, fallback) => raw != null ? String(raw) : fallback;
|
|
28
|
+
|
|
29
|
+
// The unfilled track is rendered at a lighter emphasis in the design.
|
|
30
|
+
const TRACK_EMPHASIS = {
|
|
31
|
+
Emphasis: 'Low'
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Slider — Figma node 5373:446 ("Slider").
|
|
36
|
+
*
|
|
37
|
+
* A horizontal, single-thumb slider driven entirely by design tokens. It maps a
|
|
38
|
+
* numeric range (`minValue`–`maxValue`) onto a track with a filled indicator, a
|
|
39
|
+
* draggable handle, a value bubble pinned above the handle, and optional min/max
|
|
40
|
+
* labels beneath the track. Built on `PanResponder` so the one code path works
|
|
41
|
+
* on iOS, Android and the web.
|
|
42
|
+
*
|
|
43
|
+
* The design only labels the range bounds, but a slider's purpose is to bind a
|
|
44
|
+
* value — so the live value is surfaced through the bubble (formatted via
|
|
45
|
+
* `formatOptions` / `formatValue`) and reported through `onChange` /
|
|
46
|
+
* `onChangeEnd`. Supports controlled and uncontrolled usage, web keyboard
|
|
47
|
+
* control, and an imperative {@link SliderHandle} `ref` for on-demand reads.
|
|
48
|
+
*/
|
|
49
|
+
const Slider = /*#__PURE__*/forwardRef(function Slider({
|
|
50
|
+
value: controlledValue,
|
|
51
|
+
defaultValue,
|
|
52
|
+
onChange,
|
|
53
|
+
onChangeEnd,
|
|
54
|
+
minValue = 0,
|
|
55
|
+
maxValue = 100,
|
|
56
|
+
step = 1,
|
|
57
|
+
isDisabled = false,
|
|
58
|
+
formatOptions,
|
|
59
|
+
locale,
|
|
60
|
+
formatValue,
|
|
61
|
+
renderTooltip,
|
|
62
|
+
alwaysShowTooltip = true,
|
|
63
|
+
showLabels = true,
|
|
64
|
+
minLabel,
|
|
65
|
+
maxLabel,
|
|
66
|
+
width = '100%',
|
|
67
|
+
modes = EMPTY_MODES,
|
|
68
|
+
style,
|
|
69
|
+
accessibilityLabel
|
|
70
|
+
}, ref) {
|
|
71
|
+
const tokens = useMemo(() => resolveTokens(modes), [modes]);
|
|
72
|
+
const isControlled = controlledValue !== undefined;
|
|
73
|
+
const [internalValue, setInternalValue] = useState(() => clamp(defaultValue ?? minValue, minValue, maxValue));
|
|
74
|
+
const rawValue = isControlled ? controlledValue : internalValue;
|
|
75
|
+
const currentValue = clamp(rawValue, minValue, maxValue);
|
|
76
|
+
const [trackWidth, setTrackWidth] = useState(0);
|
|
77
|
+
const [tooltipWidth, setTooltipWidth] = useState(0);
|
|
78
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
79
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
80
|
+
const quantize = useCallback(v => {
|
|
81
|
+
if (!step || step <= 0) return clamp(v, minValue, maxValue);
|
|
82
|
+
const stepped = Math.round((v - minValue) / step) * step + minValue;
|
|
83
|
+
// Round away float dust introduced by the division (e.g. 0.30000000000004).
|
|
84
|
+
const decimals = (String(step).split('.')[1] || '').length;
|
|
85
|
+
const fixed = decimals > 0 ? Number(stepped.toFixed(decimals)) : stepped;
|
|
86
|
+
return clamp(fixed, minValue, maxValue);
|
|
87
|
+
}, [minValue, maxValue, step]);
|
|
88
|
+
|
|
89
|
+
// Keep the latest interactive config in a ref so the PanResponder (created
|
|
90
|
+
// once) always reads fresh values without being recreated mid-gesture.
|
|
91
|
+
const interaction = useRef({
|
|
92
|
+
trackWidth,
|
|
93
|
+
minValue,
|
|
94
|
+
maxValue,
|
|
95
|
+
isDisabled,
|
|
96
|
+
isControlled,
|
|
97
|
+
quantize,
|
|
98
|
+
onChange,
|
|
99
|
+
onChangeEnd
|
|
100
|
+
});
|
|
101
|
+
interaction.current = {
|
|
102
|
+
trackWidth,
|
|
103
|
+
minValue,
|
|
104
|
+
maxValue,
|
|
105
|
+
isDisabled,
|
|
106
|
+
isControlled,
|
|
107
|
+
quantize,
|
|
108
|
+
onChange,
|
|
109
|
+
onChangeEnd
|
|
110
|
+
};
|
|
111
|
+
const lastValueRef = useRef(currentValue);
|
|
112
|
+
lastValueRef.current = currentValue;
|
|
113
|
+
const commit = useCallback((next, isFinal) => {
|
|
114
|
+
const {
|
|
115
|
+
isControlled: ctrl,
|
|
116
|
+
onChange: change,
|
|
117
|
+
onChangeEnd: changeEnd
|
|
118
|
+
} = interaction.current;
|
|
119
|
+
const changed = next !== lastValueRef.current;
|
|
120
|
+
lastValueRef.current = next;
|
|
121
|
+
if (changed) {
|
|
122
|
+
if (!ctrl) setInternalValue(next);
|
|
123
|
+
change?.(next);
|
|
124
|
+
}
|
|
125
|
+
if (isFinal) changeEnd?.(next);
|
|
126
|
+
}, []);
|
|
127
|
+
const valueFromX = useCallback(x => {
|
|
128
|
+
const {
|
|
129
|
+
trackWidth: w,
|
|
130
|
+
minValue: min,
|
|
131
|
+
maxValue: max,
|
|
132
|
+
quantize: q
|
|
133
|
+
} = interaction.current;
|
|
134
|
+
if (w <= 0) return lastValueRef.current;
|
|
135
|
+
const ratio = clamp(x / w, 0, 1);
|
|
136
|
+
return q(min + ratio * (max - min));
|
|
137
|
+
}, []);
|
|
138
|
+
const panResponder = useRef(PanResponder.create({
|
|
139
|
+
onStartShouldSetPanResponder: () => !interaction.current.isDisabled,
|
|
140
|
+
onMoveShouldSetPanResponder: () => !interaction.current.isDisabled,
|
|
141
|
+
onPanResponderGrant: e => {
|
|
142
|
+
if (interaction.current.isDisabled) return;
|
|
143
|
+
setIsDragging(true);
|
|
144
|
+
commit(valueFromX(e.nativeEvent.locationX), false);
|
|
145
|
+
},
|
|
146
|
+
onPanResponderMove: e => {
|
|
147
|
+
if (interaction.current.isDisabled) return;
|
|
148
|
+
commit(valueFromX(e.nativeEvent.locationX), false);
|
|
149
|
+
},
|
|
150
|
+
onPanResponderRelease: e => {
|
|
151
|
+
setIsDragging(false);
|
|
152
|
+
if (interaction.current.isDisabled) return;
|
|
153
|
+
commit(valueFromX(e.nativeEvent.locationX), true);
|
|
154
|
+
},
|
|
155
|
+
onPanResponderTerminate: () => {
|
|
156
|
+
setIsDragging(false);
|
|
157
|
+
if (interaction.current.isDisabled) return;
|
|
158
|
+
commit(lastValueRef.current, true);
|
|
159
|
+
}
|
|
160
|
+
})).current;
|
|
161
|
+
|
|
162
|
+
// Keyboard support (web). Arrow/Page keys nudge, Home/End jump to bounds.
|
|
163
|
+
const handleKeyDown = useCallback(e => {
|
|
164
|
+
if (isDisabled) return;
|
|
165
|
+
const big = Math.max(step, (maxValue - minValue) / 10);
|
|
166
|
+
let next = null;
|
|
167
|
+
switch (e.key) {
|
|
168
|
+
case 'ArrowRight':
|
|
169
|
+
case 'ArrowUp':
|
|
170
|
+
next = quantize(currentValue + step);
|
|
171
|
+
break;
|
|
172
|
+
case 'ArrowLeft':
|
|
173
|
+
case 'ArrowDown':
|
|
174
|
+
next = quantize(currentValue - step);
|
|
175
|
+
break;
|
|
176
|
+
case 'PageUp':
|
|
177
|
+
next = quantize(currentValue + big);
|
|
178
|
+
break;
|
|
179
|
+
case 'PageDown':
|
|
180
|
+
next = quantize(currentValue - big);
|
|
181
|
+
break;
|
|
182
|
+
case 'Home':
|
|
183
|
+
next = minValue;
|
|
184
|
+
break;
|
|
185
|
+
case 'End':
|
|
186
|
+
next = maxValue;
|
|
187
|
+
break;
|
|
188
|
+
default:
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
e.preventDefault?.();
|
|
192
|
+
commit(next, true);
|
|
193
|
+
}, [isDisabled, step, minValue, maxValue, currentValue, quantize, commit]);
|
|
194
|
+
const handleAccessibilityAction = useCallback(event => {
|
|
195
|
+
if (isDisabled) return;
|
|
196
|
+
const name = event.nativeEvent.actionName;
|
|
197
|
+
if (name === 'increment') commit(quantize(currentValue + step), true);else if (name === 'decrement') commit(quantize(currentValue - step), true);
|
|
198
|
+
}, [isDisabled, step, currentValue, quantize, commit]);
|
|
199
|
+
useImperativeHandle(ref, () => ({
|
|
200
|
+
getValue: () => lastValueRef.current,
|
|
201
|
+
setValue: v => commit(quantize(v), true),
|
|
202
|
+
increment: by => commit(quantize(lastValueRef.current + (by ?? step)), true),
|
|
203
|
+
decrement: by => commit(quantize(lastValueRef.current - (by ?? step)), true)
|
|
204
|
+
}), [quantize, commit, step]);
|
|
205
|
+
const defaultFormat = useCallback(v => {
|
|
206
|
+
try {
|
|
207
|
+
return new Intl.NumberFormat(locale, formatOptions).format(v);
|
|
208
|
+
} catch {
|
|
209
|
+
return String(v);
|
|
210
|
+
}
|
|
211
|
+
}, [locale, formatOptions]);
|
|
212
|
+
const format = formatValue ?? defaultFormat;
|
|
213
|
+
const ratio = maxValue > minValue ? (currentValue - minValue) / (maxValue - minValue) : 0;
|
|
214
|
+
const percent = clamp(ratio, 0, 1) * 100;
|
|
215
|
+
const handleSize = tokens.handleSize;
|
|
216
|
+
// The bubble floats (absolutely positioned, no reserved layout space). When
|
|
217
|
+
// `alwaysShowTooltip` is false it only appears while interacting: dragging on
|
|
218
|
+
// any platform, or hovering on web. Lifting the finger clears `isDragging`,
|
|
219
|
+
// so the bubble disappears immediately on touch release.
|
|
220
|
+
const showTooltip = alwaysShowTooltip || isDragging || isHovered;
|
|
221
|
+
const onTrackLayout = useCallback(e => {
|
|
222
|
+
setTrackWidth(e.nativeEvent.layout.width);
|
|
223
|
+
}, []);
|
|
224
|
+
const onTooltipLayout = useCallback(e => {
|
|
225
|
+
setTooltipWidth(e.nativeEvent.layout.width);
|
|
226
|
+
}, []);
|
|
227
|
+
const valueText = format(currentValue);
|
|
228
|
+
const tooltipNode = showTooltip ? /*#__PURE__*/_jsxs(View, {
|
|
229
|
+
onLayout: onTooltipLayout,
|
|
230
|
+
style: [styles.tooltip, {
|
|
231
|
+
backgroundColor: tokens.tooltipBackground,
|
|
232
|
+
paddingHorizontal: tokens.tooltipPaddingH,
|
|
233
|
+
paddingVertical: tokens.tooltipPaddingV,
|
|
234
|
+
borderRadius: tokens.tooltipRadius,
|
|
235
|
+
maxWidth: tokens.tooltipMaxWidth,
|
|
236
|
+
bottom: handleSize + tokens.tooltipWrapGap,
|
|
237
|
+
// Pinned to the value point on the full-width track area (not
|
|
238
|
+
// the tiny handle-width thumb). An absolute child with only
|
|
239
|
+
// `left` set is clamped by Yoga to `parentWidth - left`, so
|
|
240
|
+
// parenting it to the 20px thumb collapsed the text to ~0px
|
|
241
|
+
// and rendered an empty bubble on device. Centre via translate.
|
|
242
|
+
left: `${percent}%`,
|
|
243
|
+
transform: [{
|
|
244
|
+
translateX: -tooltipWidth / 2
|
|
245
|
+
}]
|
|
246
|
+
}],
|
|
247
|
+
pointerEvents: "none",
|
|
248
|
+
children: [renderTooltip ? renderTooltip(currentValue) : /*#__PURE__*/_jsx(Text, {
|
|
249
|
+
style: tokens.tooltipLabel,
|
|
250
|
+
numberOfLines: 1,
|
|
251
|
+
children: valueText
|
|
252
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
253
|
+
style: [styles.tip, {
|
|
254
|
+
bottom: -tokens.tipHeight
|
|
255
|
+
}],
|
|
256
|
+
pointerEvents: "none",
|
|
257
|
+
children: /*#__PURE__*/_jsx(Svg, {
|
|
258
|
+
width: tokens.tipWidth,
|
|
259
|
+
height: tokens.tipHeight,
|
|
260
|
+
viewBox: `0 0 ${tokens.tipWidth} ${tokens.tipHeight}`,
|
|
261
|
+
children: /*#__PURE__*/_jsx(Path, {
|
|
262
|
+
d: `M0 0 L${tokens.tipWidth / 2} ${tokens.tipHeight} L${tokens.tipWidth} 0 Z`,
|
|
263
|
+
fill: tokens.tooltipBackground
|
|
264
|
+
})
|
|
265
|
+
})
|
|
266
|
+
})]
|
|
267
|
+
}) : null;
|
|
268
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
269
|
+
style: [{
|
|
270
|
+
width,
|
|
271
|
+
gap: tokens.gap,
|
|
272
|
+
paddingTop: tokens.paddingTop,
|
|
273
|
+
paddingBottom: tokens.paddingBottom,
|
|
274
|
+
paddingLeft: tokens.paddingLeft,
|
|
275
|
+
paddingRight: tokens.paddingRight,
|
|
276
|
+
opacity: isDisabled ? 0.5 : 1
|
|
277
|
+
}, style],
|
|
278
|
+
accessibilityLabel: accessibilityLabel,
|
|
279
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
280
|
+
...panResponder.panHandlers,
|
|
281
|
+
onLayout: onTrackLayout,
|
|
282
|
+
style: [styles.trackArea, {
|
|
283
|
+
height: handleSize
|
|
284
|
+
}],
|
|
285
|
+
accessible: true,
|
|
286
|
+
accessibilityRole: "adjustable",
|
|
287
|
+
accessibilityLabel: accessibilityLabel,
|
|
288
|
+
accessibilityValue: {
|
|
289
|
+
min: minValue,
|
|
290
|
+
max: maxValue,
|
|
291
|
+
now: currentValue,
|
|
292
|
+
text: valueText
|
|
293
|
+
},
|
|
294
|
+
accessibilityState: {
|
|
295
|
+
disabled: isDisabled
|
|
296
|
+
},
|
|
297
|
+
accessibilityActions: [{
|
|
298
|
+
name: 'increment'
|
|
299
|
+
}, {
|
|
300
|
+
name: 'decrement'
|
|
301
|
+
}],
|
|
302
|
+
onAccessibilityAction: handleAccessibilityAction,
|
|
303
|
+
focusable: !isDisabled,
|
|
304
|
+
...(Platform.OS === 'web' ? {
|
|
305
|
+
onKeyDown: handleKeyDown,
|
|
306
|
+
tabIndex: isDisabled ? -1 : 0,
|
|
307
|
+
onMouseEnter: () => !isDisabled && setIsHovered(true),
|
|
308
|
+
onMouseLeave: () => setIsHovered(false)
|
|
309
|
+
} : {}),
|
|
310
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
311
|
+
style: {
|
|
312
|
+
height: tokens.trackHeight,
|
|
313
|
+
borderRadius: tokens.trackRadius,
|
|
314
|
+
backgroundColor: tokens.trackBackground
|
|
315
|
+
}
|
|
316
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
317
|
+
pointerEvents: "none",
|
|
318
|
+
style: {
|
|
319
|
+
position: 'absolute',
|
|
320
|
+
left: 0,
|
|
321
|
+
top: (handleSize - tokens.indicatorHeight) / 2,
|
|
322
|
+
height: tokens.indicatorHeight,
|
|
323
|
+
width: `${percent}%`,
|
|
324
|
+
borderRadius: tokens.indicatorRadius,
|
|
325
|
+
backgroundColor: tokens.indicatorBackground
|
|
326
|
+
}
|
|
327
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
328
|
+
pointerEvents: "none",
|
|
329
|
+
style: {
|
|
330
|
+
position: 'absolute',
|
|
331
|
+
top: 0,
|
|
332
|
+
left: `${percent}%`,
|
|
333
|
+
width: handleSize,
|
|
334
|
+
height: handleSize,
|
|
335
|
+
transform: [{
|
|
336
|
+
translateX: -handleSize / 2
|
|
337
|
+
}]
|
|
338
|
+
},
|
|
339
|
+
children: /*#__PURE__*/_jsx(View, {
|
|
340
|
+
style: {
|
|
341
|
+
width: handleSize,
|
|
342
|
+
height: handleSize,
|
|
343
|
+
borderRadius: tokens.handleRadius,
|
|
344
|
+
backgroundColor: tokens.handleBackground
|
|
345
|
+
}
|
|
346
|
+
})
|
|
347
|
+
}), tooltipNode]
|
|
348
|
+
}), showLabels ? /*#__PURE__*/_jsxs(View, {
|
|
349
|
+
style: styles.labelWrap,
|
|
350
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
351
|
+
style: tokens.label,
|
|
352
|
+
children: minLabel ?? format(minValue)
|
|
353
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
354
|
+
style: tokens.label,
|
|
355
|
+
children: maxLabel ?? format(maxValue)
|
|
356
|
+
})]
|
|
357
|
+
}) : null]
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
function resolveTokens(modes) {
|
|
361
|
+
// NOTE: token names are passed as string literals DIRECTLY to
|
|
362
|
+
// getVariableByName so the `extract-component-tokens` script can statically
|
|
363
|
+
// collect them for the generated docs. Do not refactor into a helper that
|
|
364
|
+
// receives the name as a variable.
|
|
365
|
+
const gap = asNum(getVariableByName('slider/gap', modes), 16);
|
|
366
|
+
const paddingTop = asNum(getVariableByName('slider/padding/top', modes), 8);
|
|
367
|
+
const paddingBottom = asNum(getVariableByName('slider/padding/bottom', modes), 0);
|
|
368
|
+
const paddingLeft = asNum(getVariableByName('slider/padding/left', modes), 0);
|
|
369
|
+
const paddingRight = asNum(getVariableByName('slider/padding/right', modes), 0);
|
|
370
|
+
const trackHeight = asNum(getVariableByName('slider/track/height', modes), 4);
|
|
371
|
+
const trackRadius = asNum(getVariableByName('slider/track/radius', modes), 999);
|
|
372
|
+
// The unfilled track uses a lighter emphasis in the design; consumer modes
|
|
373
|
+
// still win over the baked-in `Emphasis: Low`.
|
|
374
|
+
const trackBackground = asStr(getVariableByName('slider/track/background', {
|
|
375
|
+
...TRACK_EMPHASIS,
|
|
376
|
+
...modes
|
|
377
|
+
}), '#fde8c9');
|
|
378
|
+
const indicatorHeight = asNum(getVariableByName('slider/indicator/height', modes), 4);
|
|
379
|
+
const indicatorRadius = asNum(getVariableByName('slider/indicator/radius', modes), 999);
|
|
380
|
+
const indicatorBackground = asStr(getVariableByName('slider/indicator/background', modes), '#f7ab21');
|
|
381
|
+
const handleSize = asNum(getVariableByName('slider/handle/size', modes), 20);
|
|
382
|
+
const handleRadius = asNum(getVariableByName('slider/handle/radius', modes), 999999);
|
|
383
|
+
const handleBackground = asStr(getVariableByName('slider/handle/background', modes), '#f7ab21');
|
|
384
|
+
const tooltipWrapGap = asNum(getVariableByName('slider/tooltipWrap/gap', modes), 12);
|
|
385
|
+
const tooltipBackground = asStr(getVariableByName('tooltip/background', modes), '#0f0d0a');
|
|
386
|
+
const tooltipPaddingH = asNum(getVariableByName('tooltip/padding/horizontal', modes), 12);
|
|
387
|
+
const tooltipPaddingV = asNum(getVariableByName('tooltip/padding/vertical', modes), 8);
|
|
388
|
+
const tooltipRadius = asNum(getVariableByName('radius', modes), 8);
|
|
389
|
+
const tooltipMaxWidth = asNum(getVariableByName('maxWidth', modes), 280);
|
|
390
|
+
const tipWidth = asNum(getVariableByName('tooltip/tipItem/width', modes), 16);
|
|
391
|
+
const tipHeight = asNum(getVariableByName('tooltip/tipItem/height', modes), 8);
|
|
392
|
+
const tooltipLabelColor = asStr(getVariableByName('tooltip/label/color', modes), '#ffffff');
|
|
393
|
+
const tooltipLabelSize = asNum(getVariableByName('tooltip/label/fontSize', modes), 14);
|
|
394
|
+
const tooltipLabelFamily = asStr(getVariableByName('tooltip/lable/fontFamily', modes), 'JioType Var');
|
|
395
|
+
const tooltipLabelLineHeight = asNum(getVariableByName('tooltip/label/lineHeight', modes), 18);
|
|
396
|
+
const tooltipLabelWeight = asStr(getVariableByName('tooltip/label/fontWeight', modes), '500');
|
|
397
|
+
const labelColor = asStr(getVariableByName('text/foreground', modes), '#000000');
|
|
398
|
+
const labelSize = asNum(getVariableByName('text/fontSize', modes), 14);
|
|
399
|
+
const labelFamily = asStr(getVariableByName('text/fontFamily', modes), 'JioType Var');
|
|
400
|
+
const labelLineHeight = asNum(getVariableByName('text/lineHeight', modes), 20);
|
|
401
|
+
const labelWeight = asStr(getVariableByName('text/fontWeight', modes), '500');
|
|
402
|
+
const labelLetterSpacing = asNum(getVariableByName('text/letterSpacing', modes), -0.5);
|
|
403
|
+
return {
|
|
404
|
+
gap,
|
|
405
|
+
paddingTop,
|
|
406
|
+
paddingBottom,
|
|
407
|
+
paddingLeft,
|
|
408
|
+
paddingRight,
|
|
409
|
+
trackHeight,
|
|
410
|
+
trackRadius,
|
|
411
|
+
trackBackground,
|
|
412
|
+
indicatorHeight,
|
|
413
|
+
indicatorRadius,
|
|
414
|
+
indicatorBackground,
|
|
415
|
+
handleSize,
|
|
416
|
+
handleRadius,
|
|
417
|
+
handleBackground,
|
|
418
|
+
tooltipWrapGap,
|
|
419
|
+
tooltipBackground,
|
|
420
|
+
tooltipPaddingH,
|
|
421
|
+
tooltipPaddingV,
|
|
422
|
+
tooltipRadius,
|
|
423
|
+
tooltipMaxWidth,
|
|
424
|
+
tooltipLineHeight: tooltipLabelLineHeight,
|
|
425
|
+
tipWidth,
|
|
426
|
+
tipHeight,
|
|
427
|
+
tooltipLabel: {
|
|
428
|
+
color: tooltipLabelColor,
|
|
429
|
+
fontSize: tooltipLabelSize,
|
|
430
|
+
fontFamily: tooltipLabelFamily,
|
|
431
|
+
lineHeight: tooltipLabelLineHeight,
|
|
432
|
+
fontWeight: tooltipLabelWeight,
|
|
433
|
+
includeFontPadding: false
|
|
434
|
+
},
|
|
435
|
+
label: {
|
|
436
|
+
color: labelColor,
|
|
437
|
+
fontSize: labelSize,
|
|
438
|
+
fontFamily: labelFamily,
|
|
439
|
+
lineHeight: labelLineHeight,
|
|
440
|
+
fontWeight: labelWeight,
|
|
441
|
+
letterSpacing: labelLetterSpacing,
|
|
442
|
+
includeFontPadding: false
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
const styles = {
|
|
447
|
+
trackArea: {
|
|
448
|
+
width: '100%',
|
|
449
|
+
justifyContent: 'center',
|
|
450
|
+
position: 'relative'
|
|
451
|
+
},
|
|
452
|
+
labelWrap: {
|
|
453
|
+
flexDirection: 'row',
|
|
454
|
+
justifyContent: 'space-between',
|
|
455
|
+
alignItems: 'flex-start',
|
|
456
|
+
width: '100%'
|
|
457
|
+
},
|
|
458
|
+
tooltip: {
|
|
459
|
+
position: 'absolute',
|
|
460
|
+
alignItems: 'center',
|
|
461
|
+
justifyContent: 'center'
|
|
462
|
+
},
|
|
463
|
+
tip: {
|
|
464
|
+
position: 'absolute',
|
|
465
|
+
alignSelf: 'center'
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
export default Slider;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import React, { useRef, useState } from 'react';
|
|
3
|
+
import React, { forwardRef, useRef, useState } from 'react';
|
|
4
4
|
import { Platform, Pressable, View, TextInput as RNTextInput } from 'react-native';
|
|
5
5
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
6
|
import Icon from '../../icons/Icon';
|
|
7
|
-
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
|
|
7
|
+
import { EMPTY_MODES, cloneChildrenWithModes, mergeRefs } from '../../utils/react-utils';
|
|
8
8
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
9
9
|
/**
|
|
10
10
|
* TextInput component that mirrors the Figma "textInput" component.
|
|
@@ -95,7 +95,7 @@ function makePlaceholderColor(color, opacity = 0.5) {
|
|
|
95
95
|
// Fallback: return original color
|
|
96
96
|
return color;
|
|
97
97
|
}
|
|
98
|
-
function TextInput({
|
|
98
|
+
const TextInputBase = /*#__PURE__*/forwardRef(function TextInput({
|
|
99
99
|
placeholder = '',
|
|
100
100
|
value = '',
|
|
101
101
|
onChangeText,
|
|
@@ -110,7 +110,7 @@ function TextInput({
|
|
|
110
110
|
accessibilityLabel,
|
|
111
111
|
accessibilityHint,
|
|
112
112
|
...rest
|
|
113
|
-
}) {
|
|
113
|
+
}, ref) {
|
|
114
114
|
// Track focus state to hide placeholder when focused
|
|
115
115
|
const [isFocused, setIsFocused] = useState(false);
|
|
116
116
|
const [isHovered, setIsHovered] = useState(false);
|
|
@@ -225,7 +225,7 @@ function TextInput({
|
|
|
225
225
|
importantForAccessibility: "no",
|
|
226
226
|
children: processedLeading
|
|
227
227
|
}), /*#__PURE__*/_jsx(RNTextInput, {
|
|
228
|
-
ref: inputRef,
|
|
228
|
+
ref: mergeRefs(inputRef, ref),
|
|
229
229
|
accessibilityLabel: undefined,
|
|
230
230
|
accessibilityHint: accessibilityHint,
|
|
231
231
|
placeholder: displayPlaceholder,
|
|
@@ -258,7 +258,7 @@ function TextInput({
|
|
|
258
258
|
style: containerStyleArray,
|
|
259
259
|
children: inner
|
|
260
260
|
});
|
|
261
|
-
}
|
|
261
|
+
});
|
|
262
262
|
|
|
263
263
|
/**
|
|
264
264
|
* TextInput.Search component that mirrors the Figma "textInput.search" component.
|
|
@@ -280,7 +280,7 @@ function TextInput({
|
|
|
280
280
|
* @param {Object} [props.rest] - Additional props passed to the underlying TextInput
|
|
281
281
|
*/
|
|
282
282
|
|
|
283
|
-
function TextInputSearch({
|
|
283
|
+
const TextInputSearch = /*#__PURE__*/forwardRef(function TextInputSearch({
|
|
284
284
|
placeholder = 'Search',
|
|
285
285
|
value = '',
|
|
286
286
|
onChangeText,
|
|
@@ -293,7 +293,7 @@ function TextInputSearch({
|
|
|
293
293
|
onFocus,
|
|
294
294
|
onBlur,
|
|
295
295
|
...rest
|
|
296
|
-
}) {
|
|
296
|
+
}, ref) {
|
|
297
297
|
// Resolve icon tokens for default search icon
|
|
298
298
|
const iconColor = getVariableByName('textInput/icon/color', modes) || '#24262b';
|
|
299
299
|
const iconSize = getVariableByName('textInput/icon/size', modes) || 18;
|
|
@@ -330,10 +330,15 @@ function TextInputSearch({
|
|
|
330
330
|
textInputProps.accessibilityHint = accessibilityHint;
|
|
331
331
|
}
|
|
332
332
|
return /*#__PURE__*/_jsx(TextInput, {
|
|
333
|
+
ref: ref,
|
|
333
334
|
...textInputProps
|
|
334
335
|
});
|
|
335
|
-
}
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// Attach Search as a property of TextInput using namespace pattern. The base
|
|
339
|
+
// component is created via forwardRef, so we compose the static onto a typed
|
|
340
|
+
// alias to keep `TextInput.Search` strongly typed.
|
|
336
341
|
|
|
337
|
-
|
|
342
|
+
const TextInput = TextInputBase;
|
|
338
343
|
TextInput.Search = TextInputSearch;
|
|
339
344
|
export default TextInput;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React, { useMemo } from 'react';
|
|
4
|
+
import { Text as RNText } from 'react-native';
|
|
5
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
|
+
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A single coloured run inside a {@link TextSegment}. Each run can carry its own
|
|
10
|
+
* `modes`, so individual fragments of the same paragraph can resolve different
|
|
11
|
+
* design tokens (most commonly a different `text/foreground` colour) while still
|
|
12
|
+
* flowing — and wrapping — inline as one continuous line of text.
|
|
13
|
+
*/
|
|
14
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
15
|
+
const TEXT_ALIGN_MAP = {
|
|
16
|
+
Left: 'left',
|
|
17
|
+
Center: 'center'
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Resolves the `text/*` design tokens for a given modes object into an RN
|
|
22
|
+
* `TextStyle`. Shared by the outer paragraph and every inline run so a run that
|
|
23
|
+
* only overrides colour still inherits the family/size/weight/spacing.
|
|
24
|
+
*/
|
|
25
|
+
function resolveTextStyle(modes) {
|
|
26
|
+
return {
|
|
27
|
+
color: getVariableByName('text/foreground', modes) ?? '#000000',
|
|
28
|
+
fontFamily: getVariableByName('text/fontFamily', modes) ?? 'JioType',
|
|
29
|
+
fontSize: getVariableByName('text/fontSize', modes) ?? 14,
|
|
30
|
+
fontWeight: String(getVariableByName('text/fontWeight', modes) ?? '500'),
|
|
31
|
+
lineHeight: getVariableByName('text/lineHeight', modes) ?? 20,
|
|
32
|
+
letterSpacing: getVariableByName('text/letterSpacing', modes) ?? -0.5
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* TextSegment — composes several differently-styled text runs into a single
|
|
38
|
+
* paragraph that wraps as one continuous line.
|
|
39
|
+
*
|
|
40
|
+
* On the web you would reach for `<span>`s inside a `<p>`; the React Native
|
|
41
|
+
* equivalent is nesting `<Text>` inside a parent `<Text>`. That nesting is the
|
|
42
|
+
* whole trick: sibling `<View>`s would lay out as flex blocks and break onto
|
|
43
|
+
* rigid rows, but nested `<Text>` nodes flow inline and wrap naturally at word
|
|
44
|
+
* boundaries — exactly like a real paragraph — while each nested run keeps its
|
|
45
|
+
* own colour/weight resolved from its own `modes`.
|
|
46
|
+
*
|
|
47
|
+
* Two equivalent ways to author content:
|
|
48
|
+
* - **`segments` prop** — declarative array of `{ text, modes }` runs.
|
|
49
|
+
* - **`children`** — pass library `Text` elements (or strings); the parent
|
|
50
|
+
* `modes` cascade down and each child may override its own.
|
|
51
|
+
*
|
|
52
|
+
* @example Data-driven
|
|
53
|
+
* ```tsx
|
|
54
|
+
* <TextSegment
|
|
55
|
+
* segments={[
|
|
56
|
+
* { text: 'Upsell message ' },
|
|
57
|
+
* { text: 'JioFinance+', modes: { 'Text Appearance': 'Primary' } },
|
|
58
|
+
* ]}
|
|
59
|
+
* />
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @example Compositional
|
|
63
|
+
* ```tsx
|
|
64
|
+
* <TextSegment numberOfLines={2}>
|
|
65
|
+
* <Text>Pay with </Text>
|
|
66
|
+
* <Text modes={{ 'Text Appearance': 'Primary' }}>JioFinance+</Text>
|
|
67
|
+
* <Text> and earn rewards on every transaction.</Text>
|
|
68
|
+
* </TextSegment>
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
function TextSegment({
|
|
72
|
+
segments,
|
|
73
|
+
children,
|
|
74
|
+
textAlign = 'Left',
|
|
75
|
+
numberOfLines,
|
|
76
|
+
modes = EMPTY_MODES,
|
|
77
|
+
style
|
|
78
|
+
}) {
|
|
79
|
+
const baseStyle = useMemo(() => ({
|
|
80
|
+
...resolveTextStyle(modes),
|
|
81
|
+
textAlign: TEXT_ALIGN_MAP[textAlign]
|
|
82
|
+
}), [modes, textAlign]);
|
|
83
|
+
|
|
84
|
+
// children win over `segments` so the compositional API can always override.
|
|
85
|
+
const hasChildren = children !== undefined && children !== null && children !== false;
|
|
86
|
+
const renderedSegments = useMemo(() => {
|
|
87
|
+
if (hasChildren || !segments?.length) return null;
|
|
88
|
+
return segments.map((run, index) => {
|
|
89
|
+
// Parent modes first, then the run's own modes override them — mirrors the
|
|
90
|
+
// merge order used by `cloneChildrenWithModes` for the children API.
|
|
91
|
+
const runModes = run.modes ? {
|
|
92
|
+
...modes,
|
|
93
|
+
...run.modes
|
|
94
|
+
} : modes;
|
|
95
|
+
const runStyle = run.modes ? resolveTextStyle(runModes) : undefined;
|
|
96
|
+
return /*#__PURE__*/_jsx(RNText, {
|
|
97
|
+
style: [runStyle, run.style],
|
|
98
|
+
children: run.text
|
|
99
|
+
}, index);
|
|
100
|
+
});
|
|
101
|
+
}, [segments, hasChildren, modes]);
|
|
102
|
+
|
|
103
|
+
// The cascade: every child (and its descendants) receives the parent modes so
|
|
104
|
+
// nested library `Text` runs resolve their tokens against the same baseline,
|
|
105
|
+
// while still honouring any per-child `modes` override.
|
|
106
|
+
const content = hasChildren ? cloneChildrenWithModes(children, modes) : renderedSegments;
|
|
107
|
+
return /*#__PURE__*/_jsx(RNText, {
|
|
108
|
+
style: [baseStyle, style],
|
|
109
|
+
numberOfLines: numberOfLines,
|
|
110
|
+
children: content
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
export default /*#__PURE__*/React.memo(TextSegment);
|