jfs-components 0.0.42 → 0.0.44
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/lib/commonjs/components/Button/Button.js +15 -1
- package/lib/commonjs/components/Checkbox/Checkbox.js +208 -0
- package/lib/commonjs/components/MoneyValue/MoneyValue.js +81 -49
- package/lib/commonjs/components/NoteInput/NoteInput.js +120 -0
- package/lib/commonjs/components/NoteInput/index.js +13 -0
- package/lib/commonjs/components/Numpad/Numpad.js +108 -0
- package/lib/commonjs/components/StatusHero/StatusHero.js +148 -0
- package/lib/commonjs/components/Tabs/TabItem.js +79 -0
- package/lib/commonjs/components/Tabs/Tabs.js +88 -0
- package/lib/commonjs/components/Toast/Toast.js +93 -0
- package/lib/commonjs/components/Toast/ToastProvider.js +61 -0
- package/lib/commonjs/components/Toast/useToast.js +61 -0
- package/lib/commonjs/components/index.js +81 -0
- package/lib/commonjs/design-tokens/JFS Variables-variables-full.json +1 -1
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/Button/Button.js +14 -1
- package/lib/module/components/Checkbox/Checkbox.js +205 -0
- package/lib/module/components/MoneyValue/MoneyValue.js +81 -49
- package/lib/module/components/NoteInput/NoteInput.js +115 -0
- package/lib/module/components/NoteInput/index.js +3 -0
- package/lib/module/components/Numpad/Numpad.js +103 -0
- package/lib/module/components/StatusHero/StatusHero.js +142 -0
- package/lib/module/components/Tabs/TabItem.js +74 -0
- package/lib/module/components/Tabs/Tabs.js +78 -0
- package/lib/module/components/Toast/Toast.js +88 -0
- package/lib/module/components/Toast/ToastProvider.js +55 -0
- package/lib/module/components/Toast/useToast.js +54 -0
- package/lib/module/components/index.js +10 -1
- package/lib/module/design-tokens/JFS Variables-variables-full.json +1 -1
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/Button/Button.d.ts +6 -1
- package/lib/typescript/src/components/Checkbox/Checkbox.d.ts +30 -0
- package/lib/typescript/src/components/MoneyValue/MoneyValue.d.ts +18 -26
- package/lib/typescript/src/components/NoteInput/NoteInput.d.ts +23 -0
- package/lib/typescript/src/components/NoteInput/index.d.ts +3 -0
- package/lib/typescript/src/components/Numpad/Numpad.d.ts +35 -0
- package/lib/typescript/src/components/StatusHero/StatusHero.d.ts +47 -0
- package/lib/typescript/src/components/Tabs/TabItem.d.ts +29 -0
- package/lib/typescript/src/components/Tabs/Tabs.d.ts +44 -0
- package/lib/typescript/src/components/Toast/Toast.d.ts +14 -0
- package/lib/typescript/src/components/Toast/ToastProvider.d.ts +11 -0
- package/lib/typescript/src/components/Toast/useToast.d.ts +24 -0
- package/lib/typescript/src/components/index.d.ts +9 -0
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/Button/Button.tsx +14 -1
- package/src/components/Checkbox/Checkbox.tsx +238 -0
- package/src/components/MoneyValue/MoneyValue.tsx +134 -79
- package/src/components/NoteInput/NoteInput.tsx +146 -0
- package/src/components/NoteInput/index.ts +2 -0
- package/src/components/Numpad/Numpad.tsx +162 -0
- package/src/components/StatusHero/StatusHero.tsx +156 -0
- package/src/components/Tabs/TabItem.tsx +96 -0
- package/src/components/Tabs/Tabs.tsx +105 -0
- package/src/components/Toast/Toast.tsx +105 -0
- package/src/components/Toast/ToastProvider.tsx +75 -0
- package/src/components/Toast/useToast.ts +80 -0
- package/src/components/index.ts +9 -0
- package/src/design-tokens/JFS Variables-variables-full.json +1 -1
- package/src/icons/registry.ts +1 -1
|
@@ -4,6 +4,7 @@ import React, { useMemo, useState } from 'react';
|
|
|
4
4
|
import { Pressable, Text, View } from 'react-native';
|
|
5
5
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
6
|
import { usePressableWebSupport } from '../../utils/web-platform-utils';
|
|
7
|
+
import Icon from '../../icons/Icon';
|
|
7
8
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
9
|
/**
|
|
9
10
|
* Button component that maps directly to the Figma design using design tokens.
|
|
@@ -30,6 +31,7 @@ function Button({
|
|
|
30
31
|
renderContent,
|
|
31
32
|
leading,
|
|
32
33
|
trailing,
|
|
34
|
+
icon,
|
|
33
35
|
modes = {},
|
|
34
36
|
onPress,
|
|
35
37
|
disabled = false,
|
|
@@ -55,6 +57,8 @@ function Button({
|
|
|
55
57
|
const lineHeight = getVariableByName('button/lineHeight', modes) || 19;
|
|
56
58
|
const fontSize = getVariableByName('button/fontSize', modes) || 16;
|
|
57
59
|
const textColor = getVariableByName('button/foreground', modes) || '#0f0d0a';
|
|
60
|
+
const iconColor = getVariableByName('button/icon/color', modes) ?? textColor;
|
|
61
|
+
const iconSize = getVariableByName('button/icon/size', modes) ?? 18;
|
|
58
62
|
const baseLabelTextStyle = {
|
|
59
63
|
color: textColor,
|
|
60
64
|
fontFamily,
|
|
@@ -180,7 +184,16 @@ function Button({
|
|
|
180
184
|
children: [leading ? /*#__PURE__*/_jsx(View, {
|
|
181
185
|
style: leadingAccessoryStyle,
|
|
182
186
|
children: leading
|
|
183
|
-
}) : null, content,
|
|
187
|
+
}) : null, content, icon ? /*#__PURE__*/_jsx(View, {
|
|
188
|
+
style: trailingAccessoryStyle,
|
|
189
|
+
children: /*#__PURE__*/_jsx(Icon, {
|
|
190
|
+
name: icon,
|
|
191
|
+
size: iconSize,
|
|
192
|
+
color: iconColor,
|
|
193
|
+
accessibilityElementsHidden: true,
|
|
194
|
+
importantForAccessibility: "no"
|
|
195
|
+
})
|
|
196
|
+
}) : trailing ? /*#__PURE__*/_jsx(View, {
|
|
184
197
|
style: trailingAccessoryStyle,
|
|
185
198
|
children: trailing
|
|
186
199
|
}) : null]
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { Pressable, Platform } from 'react-native';
|
|
5
|
+
import Svg, { Path } from 'react-native-svg';
|
|
6
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Tracks whether the last user interaction was a keyboard event (Tab).
|
|
10
|
+
* Capture-phase document listeners fire before any element-level handlers,
|
|
11
|
+
* so the flag is always up-to-date when onFocus runs.
|
|
12
|
+
*/
|
|
13
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
14
|
+
function useFocusVisible() {
|
|
15
|
+
const [isFocusVisible, setIsFocusVisible] = useState(false);
|
|
16
|
+
const hadKeyboardEventRef = useRef(false);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (Platform.OS !== 'web' || typeof document === 'undefined') return;
|
|
19
|
+
const onKeyDown = e => {
|
|
20
|
+
if (e.key === 'Tab') hadKeyboardEventRef.current = true;
|
|
21
|
+
};
|
|
22
|
+
const onPointerDown = () => {
|
|
23
|
+
hadKeyboardEventRef.current = false;
|
|
24
|
+
};
|
|
25
|
+
document.addEventListener('keydown', onKeyDown, true);
|
|
26
|
+
document.addEventListener('mousedown', onPointerDown, true);
|
|
27
|
+
document.addEventListener('touchstart', onPointerDown, true);
|
|
28
|
+
return () => {
|
|
29
|
+
document.removeEventListener('keydown', onKeyDown, true);
|
|
30
|
+
document.removeEventListener('mousedown', onPointerDown, true);
|
|
31
|
+
document.removeEventListener('touchstart', onPointerDown, true);
|
|
32
|
+
};
|
|
33
|
+
}, []);
|
|
34
|
+
const onFocus = useCallback(() => {
|
|
35
|
+
if (hadKeyboardEventRef.current) setIsFocusVisible(true);
|
|
36
|
+
}, []);
|
|
37
|
+
const onBlur = useCallback(() => {
|
|
38
|
+
setIsFocusVisible(false);
|
|
39
|
+
}, []);
|
|
40
|
+
return {
|
|
41
|
+
isFocusVisible,
|
|
42
|
+
focusHandlers: {
|
|
43
|
+
onFocus,
|
|
44
|
+
onBlur
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Checkbox component that maps directly to the Figma design using design tokens.
|
|
50
|
+
*
|
|
51
|
+
* Supports 8 visual states: Idle, Hover, Focus, Selected, Selected Hover,
|
|
52
|
+
* Focus Selected, Disabled, and Disabled Active. All styling is driven by
|
|
53
|
+
* design tokens with optional mode overrides.
|
|
54
|
+
*
|
|
55
|
+
* @component
|
|
56
|
+
* @param {CheckboxProps} props
|
|
57
|
+
*/
|
|
58
|
+
function Checkbox({
|
|
59
|
+
checked: controlledChecked,
|
|
60
|
+
defaultChecked = false,
|
|
61
|
+
onValueChange,
|
|
62
|
+
disabled = false,
|
|
63
|
+
modes = {},
|
|
64
|
+
style,
|
|
65
|
+
accessibilityLabel
|
|
66
|
+
}) {
|
|
67
|
+
const isControlled = controlledChecked !== undefined;
|
|
68
|
+
const [internalChecked, setInternalChecked] = useState(defaultChecked);
|
|
69
|
+
const isChecked = isControlled ? controlledChecked : internalChecked;
|
|
70
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
71
|
+
const {
|
|
72
|
+
isFocusVisible,
|
|
73
|
+
focusHandlers
|
|
74
|
+
} = useFocusVisible();
|
|
75
|
+
const size = getVariableByName('checkbox/size', modes) ?? 18;
|
|
76
|
+
const radius = getVariableByName('checkbox/radius', modes) ?? 4;
|
|
77
|
+
const strokeWidth = getVariableByName('strokeWidth', modes) ?? 1;
|
|
78
|
+
const idleStroke = getVariableByName('checkbox/idle/stroke/color', modes) ?? '#666666';
|
|
79
|
+
const hoverFill = getVariableByName('checkbox/hover/fill/color', modes) ?? 'rgba(0,0,0,0.1)';
|
|
80
|
+
const hoverStroke = getVariableByName('checkbox/hover/stroke/color', modes) ?? '#666666';
|
|
81
|
+
const hoverRing = getVariableByName('checkbox/hoverRing', modes) ?? 4;
|
|
82
|
+
const hoverShadow = getVariableByName('hover/boxShadow', modes) ?? 'rgba(0,0,0,0.1)';
|
|
83
|
+
const selectedFill = getVariableByName('checkbox/selected/fill/color', modes) ?? '#cea15a';
|
|
84
|
+
const selectedStroke = getVariableByName('checkbox/selected/stroke/color', modes) ?? '#0d0d0d';
|
|
85
|
+
const selectedMarkColor = getVariableByName('checkbox/selected/mark/color', modes) ?? '#0f0d0a';
|
|
86
|
+
const focusFill = getVariableByName('checkbox/focus/fill/color', modes) ?? '#ffffff';
|
|
87
|
+
const focusStroke = getVariableByName('checkbox/focus/stroke/color', modes) ?? '#666666';
|
|
88
|
+
const focusShadowColor = getVariableByName('checkbox/focus/shadow/color', modes) ?? '#12fc2e';
|
|
89
|
+
const focusSpread = getVariableByName('checkbox/focus/spread/effect', modes) ?? 2;
|
|
90
|
+
const disabledStroke = getVariableByName('checkbox/disabled/stroke/color', modes) ?? '#999999';
|
|
91
|
+
const disabledActiveBg = getVariableByName('checkbox/disabledActive/background/color', modes) ?? '#f6d19a';
|
|
92
|
+
const disabledActiveBorder = getVariableByName('checkbox/disabledActive/border/color', modes) ?? '#f6d19a';
|
|
93
|
+
const disabledActiveMark = getVariableByName('checkbox/disabledActive/mark/color', modes) ?? '#474d54';
|
|
94
|
+
const selectedHoverShadow = getVariableByName('selected/hover/boxShadow', modes) ?? 'rgba(0,0,0,0.1)';
|
|
95
|
+
const hoverShadowSize = getVariableByName('hover/boxShadow/size', modes) ?? 4;
|
|
96
|
+
const handlePress = useCallback(() => {
|
|
97
|
+
if (disabled) return;
|
|
98
|
+
const next = !isChecked;
|
|
99
|
+
if (!isControlled) {
|
|
100
|
+
setInternalChecked(next);
|
|
101
|
+
}
|
|
102
|
+
onValueChange?.(next);
|
|
103
|
+
}, [disabled, isChecked, isControlled, onValueChange]);
|
|
104
|
+
const resolveStyle = () => {
|
|
105
|
+
const base = {
|
|
106
|
+
width: size,
|
|
107
|
+
height: size,
|
|
108
|
+
borderRadius: radius,
|
|
109
|
+
borderWidth: strokeWidth,
|
|
110
|
+
alignItems: 'center',
|
|
111
|
+
justifyContent: 'center',
|
|
112
|
+
overflow: 'hidden'
|
|
113
|
+
};
|
|
114
|
+
if (disabled && isChecked) {
|
|
115
|
+
return {
|
|
116
|
+
...base,
|
|
117
|
+
backgroundColor: disabledActiveBg,
|
|
118
|
+
borderColor: disabledActiveBorder
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
if (disabled) {
|
|
122
|
+
return {
|
|
123
|
+
...base,
|
|
124
|
+
borderColor: disabledStroke
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (isChecked && isFocusVisible) {
|
|
128
|
+
return {
|
|
129
|
+
...base,
|
|
130
|
+
backgroundColor: selectedFill,
|
|
131
|
+
borderColor: selectedStroke,
|
|
132
|
+
...boxShadow(`0px 0px 0px ${focusSpread}px ${focusShadowColor}`)
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (isChecked && isHovered) {
|
|
136
|
+
return {
|
|
137
|
+
...base,
|
|
138
|
+
backgroundColor: selectedFill,
|
|
139
|
+
borderColor: selectedStroke,
|
|
140
|
+
...boxShadow(`0px 0px 0px ${hoverShadowSize}px ${selectedHoverShadow}`)
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (isChecked) {
|
|
144
|
+
return {
|
|
145
|
+
...base,
|
|
146
|
+
backgroundColor: selectedFill,
|
|
147
|
+
borderColor: selectedStroke
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (isFocusVisible) {
|
|
151
|
+
return {
|
|
152
|
+
...base,
|
|
153
|
+
backgroundColor: focusFill,
|
|
154
|
+
borderColor: focusStroke,
|
|
155
|
+
...boxShadow(`0px 0px 0px ${focusSpread}px ${focusShadowColor}`)
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
if (isHovered) {
|
|
159
|
+
return {
|
|
160
|
+
...base,
|
|
161
|
+
backgroundColor: hoverFill,
|
|
162
|
+
borderColor: hoverStroke,
|
|
163
|
+
...boxShadow(`0px 0px 0px ${hoverRing}px ${hoverShadow}`)
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
...base,
|
|
168
|
+
borderColor: idleStroke
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
const markColor = disabled && isChecked ? disabledActiveMark : selectedMarkColor;
|
|
172
|
+
return /*#__PURE__*/_jsx(Pressable, {
|
|
173
|
+
style: [resolveStyle(), style],
|
|
174
|
+
onPress: handlePress,
|
|
175
|
+
disabled: disabled,
|
|
176
|
+
onHoverIn: () => setIsHovered(true),
|
|
177
|
+
onHoverOut: () => setIsHovered(false),
|
|
178
|
+
...focusHandlers,
|
|
179
|
+
accessibilityRole: "checkbox",
|
|
180
|
+
accessibilityState: {
|
|
181
|
+
checked: isChecked,
|
|
182
|
+
disabled
|
|
183
|
+
},
|
|
184
|
+
accessibilityLabel: accessibilityLabel,
|
|
185
|
+
children: isChecked && /*#__PURE__*/_jsx(Svg, {
|
|
186
|
+
width: 12,
|
|
187
|
+
height: 9,
|
|
188
|
+
viewBox: "0 0 12 9",
|
|
189
|
+
fill: "none",
|
|
190
|
+
children: /*#__PURE__*/_jsx(Path, {
|
|
191
|
+
d: "M4.00091 8.66939C3.91321 8.6699 3.82628 8.65309 3.74509 8.61991C3.6639 8.58673 3.59006 8.53785 3.52779 8.47606L0.195972 5.14273C0.0704931 5.01719 -1.86978e-09 4.84693 0 4.66939C1.86978e-09 4.49186 0.0704931 4.3216 0.195972 4.19606C0.321451 4.07053 0.491636 4 0.66909 4C0.846544 4 1.01673 4.07053 1.14221 4.19606L4.00091 7.06273L10.8578 0.196061C10.9833 0.0705253 11.1535 0 11.3309 0C11.5084 0 11.6785 0.0705253 11.804 0.196061C11.9295 0.321597 12 0.49186 12 0.669394C12 0.846929 11.9295 1.01719 11.804 1.14273L4.47403 8.47606C4.41176 8.53785 4.33792 8.58673 4.25673 8.61991C4.17554 8.65309 4.08861 8.6699 4.00091 8.66939Z",
|
|
192
|
+
fill: markColor
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
function boxShadow(value) {
|
|
198
|
+
if (Platform.OS === 'web') {
|
|
199
|
+
return {
|
|
200
|
+
boxShadow: value
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
return {};
|
|
204
|
+
}
|
|
205
|
+
export default Checkbox;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import React from 'react';
|
|
4
|
-
import { View, Text } from 'react-native';
|
|
3
|
+
import React, { useMemo, useEffect, useRef } from 'react';
|
|
4
|
+
import { View, Text, Pressable, Animated } from 'react-native';
|
|
5
5
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
6
|
|
|
7
7
|
// Map of common ISO 4217 currency codes to display symbols
|
|
@@ -42,34 +42,20 @@ const CURRENCY_SYMBOLS = {
|
|
|
42
42
|
/**
|
|
43
43
|
* MoneyValue component that mirrors the Figma MoneyValue design.
|
|
44
44
|
*
|
|
45
|
-
* The styling is fully resolved from Figma design tokens using `getVariableByName
|
|
46
|
-
*
|
|
45
|
+
* The styling is fully resolved from Figma design tokens using `getVariableByName`.
|
|
46
|
+
* Supports separate typography scaling for currency and value.
|
|
47
47
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* Negative values are auto-detected from the value prop (e.g., -500 or "-500").
|
|
53
|
-
* The `negative` prop can be used to explicitly override this behavior.
|
|
54
|
-
*
|
|
55
|
-
* @component
|
|
56
|
-
* @param {Object} props
|
|
57
|
-
* @param {string|number} [props.value="500"] - Monetary value to display. Negative values are auto-detected.
|
|
58
|
-
* @param {string} [props.currency="₹"] - Currency symbol or ISO code (e.g. "INR").
|
|
59
|
-
* @param {boolean} [props.negative] - Explicitly override negative display. If undefined, auto-detects from value.
|
|
60
|
-
* @param {Object} [props.modes={}] - Modes object passed directly to `getVariableByName`.
|
|
61
|
-
* Example: {"MoneyValue / Theme": "Default"}
|
|
62
|
-
* @param {Object} [props.style] - Optional container style overrides.
|
|
63
|
-
* @param {Object} [props.valueStyle] - Optional value text style overrides.
|
|
64
|
-
* @param {Object} [props.currencyStyle] - Optional currency text style overrides.
|
|
65
|
-
* @param {Object} [props.negativeSignStyle] - Optional negative sign text style overrides.
|
|
66
|
-
* @param {string} [props.accessibilityLabel] - Accessibility label for screen readers. If not provided, generates from value and currency
|
|
67
|
-
* @param {string} [props.accessibilityHint] - Additional accessibility hint for screen readers
|
|
48
|
+
* To make this component editable without coupling it to a Numpad component,
|
|
49
|
+
* use the `onPress` prop and manage the data state in the parent layer. When
|
|
50
|
+
* the `focused` prop is provided to this component, it will display a natural
|
|
51
|
+
* blinking text cursor.
|
|
68
52
|
*/
|
|
69
53
|
function MoneyValue({
|
|
70
54
|
value = '500',
|
|
71
55
|
currency = '₹',
|
|
72
56
|
negative,
|
|
57
|
+
focused = false,
|
|
58
|
+
hidden = false,
|
|
73
59
|
modes = {},
|
|
74
60
|
style,
|
|
75
61
|
valueStyle,
|
|
@@ -77,13 +63,14 @@ function MoneyValue({
|
|
|
77
63
|
negativeSignStyle,
|
|
78
64
|
accessibilityLabel,
|
|
79
65
|
accessibilityHint,
|
|
66
|
+
onPress,
|
|
80
67
|
...rest
|
|
81
68
|
}) {
|
|
82
69
|
// Auto-detect negative from value and compute display value
|
|
83
70
|
const {
|
|
84
71
|
isNegative,
|
|
85
72
|
displayValue
|
|
86
|
-
} =
|
|
73
|
+
} = useMemo(() => {
|
|
87
74
|
const stringValue = String(value);
|
|
88
75
|
const trimmed = stringValue.trim();
|
|
89
76
|
const valueIsNegative = trimmed.startsWith('-') || typeof value === 'number' && value < 0;
|
|
@@ -94,62 +81,107 @@ function MoneyValue({
|
|
|
94
81
|
// Use explicit negative prop if provided, otherwise use auto-detected
|
|
95
82
|
const showNegative = negative !== undefined ? negative : valueIsNegative;
|
|
96
83
|
return {
|
|
97
|
-
isNegative: showNegative,
|
|
98
|
-
displayValue: absoluteValue
|
|
84
|
+
isNegative: hidden ? false : showNegative,
|
|
85
|
+
displayValue: hidden ? '•••' : absoluteValue
|
|
99
86
|
};
|
|
100
|
-
}, [value, negative]);
|
|
87
|
+
}, [value, negative, hidden]);
|
|
101
88
|
|
|
102
89
|
// Resolve typography and layout tokens from Figma
|
|
103
90
|
const textColor = getVariableByName('moneyValue/text/color', modes) || '#0f0d0a';
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
const
|
|
91
|
+
const currencyFontSize = getVariableByName('moneyValue/currency/fontSize', modes) || 14;
|
|
92
|
+
const currencyLineHeight = getVariableByName('moneyValue/currency/lineHeight', modes) || 18;
|
|
93
|
+
const valueFontSize = getVariableByName('moneyValue/value/fontSize', modes) || 14;
|
|
94
|
+
const valueLineHeight = getVariableByName('moneyValue/value/lineHeight', modes) || 18;
|
|
95
|
+
const fontWeightValue = getVariableByName('moneyValue/fontWeight', modes) || 500;
|
|
107
96
|
const fontWeight = typeof fontWeightValue === 'number' ? fontWeightValue.toString() : fontWeightValue;
|
|
108
|
-
const fontFamily = getVariableByName('moneyValue/
|
|
97
|
+
const fontFamily = getVariableByName('moneyValue/fontFamily', modes) || 'System';
|
|
109
98
|
const gap = getVariableByName('moneyValue/gap', modes) || 4;
|
|
110
99
|
|
|
111
100
|
// Resolve currency to a symbol, supporting both symbols and ISO codes
|
|
112
|
-
const resolvedCurrency =
|
|
101
|
+
const resolvedCurrency = useMemo(() => {
|
|
113
102
|
if (!currency) return '';
|
|
114
103
|
const upper = currency.toUpperCase ? currency.toUpperCase() : currency;
|
|
115
104
|
return upper in CURRENCY_SYMBOLS ? CURRENCY_SYMBOLS[upper] : currency;
|
|
116
105
|
}, [currency]);
|
|
117
|
-
const
|
|
106
|
+
const currencyTextStyle = {
|
|
107
|
+
color: textColor,
|
|
108
|
+
fontSize: currencyFontSize,
|
|
109
|
+
lineHeight: currencyLineHeight,
|
|
110
|
+
fontWeight: fontWeight,
|
|
111
|
+
fontFamily: fontFamily
|
|
112
|
+
};
|
|
113
|
+
const valueTextStyle = {
|
|
118
114
|
color: textColor,
|
|
119
|
-
fontSize,
|
|
120
|
-
lineHeight,
|
|
121
|
-
fontWeight,
|
|
122
|
-
fontFamily
|
|
115
|
+
fontSize: valueFontSize,
|
|
116
|
+
lineHeight: valueLineHeight,
|
|
117
|
+
fontWeight: fontWeight,
|
|
118
|
+
fontFamily: fontFamily
|
|
123
119
|
};
|
|
124
120
|
const containerStyle = {
|
|
125
121
|
flexDirection: 'row',
|
|
126
122
|
alignItems: 'center',
|
|
127
|
-
gap
|
|
123
|
+
gap: gap
|
|
128
124
|
};
|
|
129
125
|
|
|
130
|
-
//
|
|
131
|
-
const
|
|
132
|
-
|
|
126
|
+
// Blinking cursor animation
|
|
127
|
+
const cursorOpacity = useRef(new Animated.Value(0)).current;
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
if (focused) {
|
|
130
|
+
const animation = Animated.loop(Animated.sequence([Animated.timing(cursorOpacity, {
|
|
131
|
+
toValue: 1,
|
|
132
|
+
duration: 400,
|
|
133
|
+
useNativeDriver: true
|
|
134
|
+
}), Animated.timing(cursorOpacity, {
|
|
135
|
+
toValue: 0,
|
|
136
|
+
duration: 400,
|
|
137
|
+
useNativeDriver: true
|
|
138
|
+
})]));
|
|
139
|
+
animation.start();
|
|
140
|
+
return () => {
|
|
141
|
+
animation.stop();
|
|
142
|
+
cursorOpacity.setValue(0);
|
|
143
|
+
};
|
|
144
|
+
} else {
|
|
145
|
+
cursorOpacity.setValue(0);
|
|
146
|
+
}
|
|
147
|
+
}, [focused, cursorOpacity]);
|
|
148
|
+
return /*#__PURE__*/_jsxs(Pressable, {
|
|
133
149
|
style: [containerStyle, style],
|
|
134
150
|
accessibilityRole: "text",
|
|
135
151
|
accessibilityLabel: undefined,
|
|
136
152
|
accessibilityHint: accessibilityHint,
|
|
153
|
+
onPress: onPress,
|
|
154
|
+
disabled: !onPress,
|
|
137
155
|
...rest,
|
|
138
156
|
children: [isNegative && /*#__PURE__*/_jsx(Text, {
|
|
139
|
-
style: [
|
|
157
|
+
style: [currencyTextStyle, negativeSignStyle],
|
|
140
158
|
accessibilityElementsHidden: true,
|
|
141
159
|
importantForAccessibility: "no",
|
|
142
160
|
children: "-"
|
|
143
161
|
}), /*#__PURE__*/_jsx(Text, {
|
|
144
|
-
style: [
|
|
162
|
+
style: [currencyTextStyle, currencyStyle],
|
|
145
163
|
accessibilityElementsHidden: true,
|
|
146
164
|
importantForAccessibility: "no",
|
|
147
165
|
children: resolvedCurrency
|
|
148
|
-
}), /*#__PURE__*/
|
|
149
|
-
style:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
166
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
167
|
+
style: {
|
|
168
|
+
flexDirection: 'row',
|
|
169
|
+
alignItems: 'center'
|
|
170
|
+
},
|
|
171
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
172
|
+
style: [valueTextStyle, valueStyle],
|
|
173
|
+
accessibilityElementsHidden: true,
|
|
174
|
+
importantForAccessibility: "no",
|
|
175
|
+
children: displayValue
|
|
176
|
+
}), focused && /*#__PURE__*/_jsx(Animated.View, {
|
|
177
|
+
style: {
|
|
178
|
+
opacity: cursorOpacity,
|
|
179
|
+
width: 2,
|
|
180
|
+
height: valueFontSize * 1.1,
|
|
181
|
+
backgroundColor: textColor,
|
|
182
|
+
marginLeft: 2
|
|
183
|
+
}
|
|
184
|
+
})]
|
|
153
185
|
})]
|
|
154
186
|
});
|
|
155
187
|
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React, { useState, useRef } from 'react';
|
|
4
|
+
import { View, TextInput as RNTextInput, Text, Pressable } from 'react-native';
|
|
5
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
7
|
+
/**
|
|
8
|
+
* NoteInput component representing an interactive "Add note" badge style field.
|
|
9
|
+
* Allows the user to click, clears the placeholder text, and shows a blinking cursor when focused.
|
|
10
|
+
*/
|
|
11
|
+
export default function NoteInput({
|
|
12
|
+
value = '',
|
|
13
|
+
placeholder = 'Add note',
|
|
14
|
+
onChangeText,
|
|
15
|
+
modes = {},
|
|
16
|
+
style,
|
|
17
|
+
textStyle,
|
|
18
|
+
state: explicitState,
|
|
19
|
+
onFocus,
|
|
20
|
+
onBlur,
|
|
21
|
+
...rest
|
|
22
|
+
}) {
|
|
23
|
+
const [internalFocused, setInternalFocused] = useState(false);
|
|
24
|
+
const inputRef = useRef(null);
|
|
25
|
+
|
|
26
|
+
// Resolve tokens from Figma Design
|
|
27
|
+
const foreground = getVariableByName('noteInput/foreground', modes) || '#0d0d0f';
|
|
28
|
+
const fontSize = getVariableByName('noteInput/fontSize', modes) || 14;
|
|
29
|
+
const fontFamily = getVariableByName('noteInput/fontFamily', modes) || 'JioType Var';
|
|
30
|
+
const lineHeight = getVariableByName('noteInput/lineHeight', modes) || 16;
|
|
31
|
+
const fontWeightRaw = getVariableByName('noteInput/fontWeight', modes) || 700;
|
|
32
|
+
const fontWeight = typeof fontWeightRaw === 'number' ? fontWeightRaw.toString() : fontWeightRaw;
|
|
33
|
+
const gap = getVariableByName('noteInput/gap', modes) || 0; // 4 in some files, 0 in context
|
|
34
|
+
const paddingHorizontal = getVariableByName('noteInput/padding/horizontal', modes) || 17;
|
|
35
|
+
const paddingVertical = getVariableByName('noteInput/padding/vertical', modes) || 9;
|
|
36
|
+
const radius = getVariableByName('noteInput/radius', modes) || 999;
|
|
37
|
+
const borderSize = getVariableByName('noteInput/border/size', modes) || 1;
|
|
38
|
+
const background = getVariableByName('noteInput/background', modes) || '#ebebed';
|
|
39
|
+
const borderColor = getVariableByName('noteInput/border/color', modes) || 'rgba(255,255,255,0)';
|
|
40
|
+
const containerStyle = {
|
|
41
|
+
flexDirection: 'row',
|
|
42
|
+
alignItems: 'center',
|
|
43
|
+
justifyContent: 'center',
|
|
44
|
+
paddingHorizontal: paddingHorizontal,
|
|
45
|
+
paddingVertical: paddingVertical,
|
|
46
|
+
borderRadius: radius,
|
|
47
|
+
backgroundColor: background,
|
|
48
|
+
borderWidth: borderSize,
|
|
49
|
+
borderColor: borderColor,
|
|
50
|
+
gap: gap,
|
|
51
|
+
// Add specific width when editing if requested by Figma design logic, though flex fits content generically
|
|
52
|
+
alignSelf: 'flex-start'
|
|
53
|
+
};
|
|
54
|
+
const baseTextStyle = {
|
|
55
|
+
color: foreground,
|
|
56
|
+
fontSize: fontSize,
|
|
57
|
+
fontFamily: fontFamily,
|
|
58
|
+
lineHeight: lineHeight,
|
|
59
|
+
fontWeight: fontWeight,
|
|
60
|
+
padding: 0,
|
|
61
|
+
margin: 0,
|
|
62
|
+
minHeight: lineHeight
|
|
63
|
+
};
|
|
64
|
+
const handleFocus = e => {
|
|
65
|
+
setInternalFocused(true);
|
|
66
|
+
if (onFocus) onFocus(e);
|
|
67
|
+
};
|
|
68
|
+
const handleBlur = e => {
|
|
69
|
+
setInternalFocused(false);
|
|
70
|
+
if (onBlur) onBlur(e);
|
|
71
|
+
};
|
|
72
|
+
const handlePress = () => {
|
|
73
|
+
inputRef.current?.focus();
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Blinking cursor setup for custom UI representation if we were drawing it natively.
|
|
77
|
+
// We use RNTextInput's native cursor, but we can style it or ensure it's visible.
|
|
78
|
+
|
|
79
|
+
return /*#__PURE__*/_jsx(Pressable, {
|
|
80
|
+
style: [containerStyle, style],
|
|
81
|
+
onPress: handlePress,
|
|
82
|
+
children: /*#__PURE__*/_jsxs(View, {
|
|
83
|
+
style: {
|
|
84
|
+
position: 'relative',
|
|
85
|
+
justifyContent: 'center'
|
|
86
|
+
},
|
|
87
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
88
|
+
style: [baseTextStyle, textStyle, {
|
|
89
|
+
opacity: 0
|
|
90
|
+
}],
|
|
91
|
+
accessibilityElementsHidden: true,
|
|
92
|
+
importantForAccessibility: "no",
|
|
93
|
+
children: internalFocused ? value || ' ' : value || placeholder
|
|
94
|
+
}), /*#__PURE__*/_jsx(RNTextInput, {
|
|
95
|
+
ref: inputRef,
|
|
96
|
+
value: value,
|
|
97
|
+
onChangeText: onChangeText,
|
|
98
|
+
placeholder: internalFocused ? '' : placeholder,
|
|
99
|
+
placeholderTextColor: foreground,
|
|
100
|
+
onFocus: handleFocus,
|
|
101
|
+
onBlur: handleBlur,
|
|
102
|
+
selectionColor: foreground,
|
|
103
|
+
style: [baseTextStyle, {
|
|
104
|
+
position: 'absolute',
|
|
105
|
+
left: 0,
|
|
106
|
+
right: 0,
|
|
107
|
+
top: 0,
|
|
108
|
+
bottom: 0,
|
|
109
|
+
outlineStyle: 'none'
|
|
110
|
+
}, textStyle],
|
|
111
|
+
...rest
|
|
112
|
+
})]
|
|
113
|
+
})
|
|
114
|
+
});
|
|
115
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React, { useMemo, useCallback } from 'react';
|
|
4
|
+
import { View, Text, Pressable } from 'react-native';
|
|
5
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
|
+
import { IconDeletebackspace } from '../../icons/components/IconDeletebackspace';
|
|
7
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
8
|
+
function shuffleArray(arr) {
|
|
9
|
+
const shuffled = [...arr];
|
|
10
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
11
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
12
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
13
|
+
}
|
|
14
|
+
return shuffled;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Secure numpad component for the JFS finance system.
|
|
19
|
+
*
|
|
20
|
+
* Renders a 3×4 grid of digit keys (0-9), an optional decimal key, and a
|
|
21
|
+
* backspace key. Digit positions are shuffled by default to guard against
|
|
22
|
+
* keylogging and shoulder-surfing attacks on mobile devices.
|
|
23
|
+
*
|
|
24
|
+
* @component
|
|
25
|
+
* @param {NumpadProps} props
|
|
26
|
+
*/
|
|
27
|
+
function Numpad({
|
|
28
|
+
modes = {},
|
|
29
|
+
onKeyPress,
|
|
30
|
+
showDecimal = true,
|
|
31
|
+
shuffle = true,
|
|
32
|
+
style,
|
|
33
|
+
keyStyle,
|
|
34
|
+
keyTextStyle
|
|
35
|
+
}) {
|
|
36
|
+
const foreground = getVariableByName('numpad/foreground', modes) ?? '#141414';
|
|
37
|
+
const lineHeight = getVariableByName('numpad/lineHeight', modes) ?? 32;
|
|
38
|
+
const fontFamily = getVariableByName('numpad/fontFamily', modes) ?? 'JioType Var';
|
|
39
|
+
const fontSize = getVariableByName('numpad/fontSize', modes) ?? 32;
|
|
40
|
+
const rowGap = getVariableByName('numpad/gridRowGap/vertical', modes) ?? 12;
|
|
41
|
+
const columnGap = getVariableByName('numpad/gridColumnGap/horizontal', modes) ?? 12;
|
|
42
|
+
const digits = useMemo(() => {
|
|
43
|
+
const base = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];
|
|
44
|
+
return shuffle ? shuffleArray(base) : base;
|
|
45
|
+
}, [shuffle]);
|
|
46
|
+
const rows = useMemo(() => [digits.slice(0, 3), digits.slice(3, 6), digits.slice(6, 9), [showDecimal ? '.' : null, digits[9], 'backspace']], [digits, showDecimal]);
|
|
47
|
+
const handlePress = useCallback(key => {
|
|
48
|
+
onKeyPress?.(key);
|
|
49
|
+
}, [onKeyPress]);
|
|
50
|
+
const textStyle = {
|
|
51
|
+
color: foreground,
|
|
52
|
+
fontFamily: fontFamily,
|
|
53
|
+
fontSize: fontSize,
|
|
54
|
+
lineHeight: lineHeight,
|
|
55
|
+
textAlign: 'center'
|
|
56
|
+
};
|
|
57
|
+
return /*#__PURE__*/_jsx(View, {
|
|
58
|
+
style: [{
|
|
59
|
+
gap: rowGap
|
|
60
|
+
}, style],
|
|
61
|
+
accessibilityRole: "none",
|
|
62
|
+
children: rows.map((row, rowIndex) => /*#__PURE__*/_jsx(View, {
|
|
63
|
+
style: {
|
|
64
|
+
flexDirection: 'row',
|
|
65
|
+
gap: columnGap
|
|
66
|
+
},
|
|
67
|
+
children: row.map((key, colIndex) => {
|
|
68
|
+
if (key === null) {
|
|
69
|
+
return /*#__PURE__*/_jsx(View, {
|
|
70
|
+
style: {
|
|
71
|
+
flex: 1
|
|
72
|
+
}
|
|
73
|
+
}, `empty-${colIndex}`);
|
|
74
|
+
}
|
|
75
|
+
const isBackspace = key === 'backspace';
|
|
76
|
+
return /*#__PURE__*/_jsx(Pressable, {
|
|
77
|
+
style: ({
|
|
78
|
+
pressed
|
|
79
|
+
}) => [{
|
|
80
|
+
flex: 1,
|
|
81
|
+
justifyContent: 'center',
|
|
82
|
+
alignItems: 'center',
|
|
83
|
+
minHeight: 46
|
|
84
|
+
}, pressed && {
|
|
85
|
+
opacity: 0.4
|
|
86
|
+
}, keyStyle],
|
|
87
|
+
onPress: () => handlePress(key),
|
|
88
|
+
accessibilityRole: "button",
|
|
89
|
+
accessibilityLabel: isBackspace ? 'Backspace' : key,
|
|
90
|
+
children: isBackspace ? /*#__PURE__*/_jsx(IconDeletebackspace, {
|
|
91
|
+
width: fontSize,
|
|
92
|
+
height: fontSize,
|
|
93
|
+
fill: foreground
|
|
94
|
+
}) : /*#__PURE__*/_jsx(Text, {
|
|
95
|
+
style: [textStyle, keyTextStyle],
|
|
96
|
+
children: key
|
|
97
|
+
})
|
|
98
|
+
}, `${key}-${colIndex}`);
|
|
99
|
+
})
|
|
100
|
+
}, rowIndex))
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
export default Numpad;
|