jfs-components 0.0.73 → 0.0.74

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CHANGELOG.md +23 -6
  2. package/lib/commonjs/components/AccountCard/AccountCard.js +247 -0
  3. package/lib/commonjs/components/AppBar/AppBar.js +17 -11
  4. package/lib/commonjs/components/CardBankAccount/CardBankAccount.js +18 -2
  5. package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +40 -25
  6. package/lib/commonjs/components/Dropdown/Dropdown.js +214 -0
  7. package/lib/commonjs/components/DropdownInput/DropdownInput.js +542 -0
  8. package/lib/commonjs/components/FormField/FormField.js +328 -178
  9. package/lib/commonjs/components/LottieIntroBlock/LottieIntroBlock.js +150 -0
  10. package/lib/commonjs/components/PageHero/PageHero.js +153 -0
  11. package/lib/commonjs/components/PoweredByLabel/PoweredByLabel.js +135 -0
  12. package/lib/commonjs/components/PoweredByLabel/finvu.png +0 -0
  13. package/lib/commonjs/components/Text/Text.js +9 -2
  14. package/lib/commonjs/components/Tooltip/Tooltip.js +34 -27
  15. package/lib/commonjs/components/index.js +60 -0
  16. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  17. package/lib/commonjs/icons/registry.js +1 -1
  18. package/lib/module/components/AccountCard/AccountCard.js +241 -0
  19. package/lib/module/components/AppBar/AppBar.js +17 -11
  20. package/lib/module/components/CardBankAccount/CardBankAccount.js +17 -2
  21. package/lib/module/components/CheckboxItem/CheckboxItem.js +41 -26
  22. package/lib/module/components/Dropdown/Dropdown.js +206 -0
  23. package/lib/module/components/DropdownInput/DropdownInput.js +536 -0
  24. package/lib/module/components/FormField/FormField.js +330 -180
  25. package/lib/module/components/LottieIntroBlock/LottieIntroBlock.js +144 -0
  26. package/lib/module/components/PageHero/PageHero.js +147 -0
  27. package/lib/module/components/PoweredByLabel/PoweredByLabel.js +130 -0
  28. package/lib/module/components/PoweredByLabel/finvu.png +0 -0
  29. package/lib/module/components/Text/Text.js +9 -2
  30. package/lib/module/components/Tooltip/Tooltip.js +34 -27
  31. package/lib/module/components/index.js +7 -1
  32. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  33. package/lib/module/icons/registry.js +1 -1
  34. package/lib/typescript/src/components/AccountCard/AccountCard.d.ts +81 -0
  35. package/lib/typescript/src/components/CardBankAccount/CardBankAccount.d.ts +9 -2
  36. package/lib/typescript/src/components/CheckboxItem/CheckboxItem.d.ts +18 -2
  37. package/lib/typescript/src/components/Dropdown/Dropdown.d.ts +62 -0
  38. package/lib/typescript/src/components/DropdownInput/DropdownInput.d.ts +107 -0
  39. package/lib/typescript/src/components/FormField/FormField.d.ts +76 -19
  40. package/lib/typescript/src/components/LottieIntroBlock/LottieIntroBlock.d.ts +58 -0
  41. package/lib/typescript/src/components/PageHero/PageHero.d.ts +53 -0
  42. package/lib/typescript/src/components/PoweredByLabel/PoweredByLabel.d.ts +70 -0
  43. package/lib/typescript/src/components/Text/Text.d.ts +12 -2
  44. package/lib/typescript/src/components/Tooltip/Tooltip.d.ts +13 -2
  45. package/lib/typescript/src/components/index.d.ts +7 -1
  46. package/lib/typescript/src/icons/registry.d.ts +1 -1
  47. package/package.json +1 -3
  48. package/src/components/AccountCard/AccountCard.tsx +376 -0
  49. package/src/components/AppBar/AppBar.tsx +25 -14
  50. package/src/components/CardBankAccount/CardBankAccount.tsx +29 -3
  51. package/src/components/CheckboxItem/CheckboxItem.tsx +65 -30
  52. package/src/components/Dropdown/Dropdown.tsx +331 -0
  53. package/src/components/DropdownInput/DropdownInput.tsx +819 -0
  54. package/src/components/FormField/FormField.tsx +542 -215
  55. package/src/components/LottieIntroBlock/LottieIntroBlock.tsx +202 -0
  56. package/src/components/PageHero/PageHero.tsx +200 -0
  57. package/src/components/PoweredByLabel/PoweredByLabel.tsx +221 -0
  58. package/src/components/PoweredByLabel/finvu.png +0 -0
  59. package/src/components/Text/Text.tsx +24 -3
  60. package/src/components/Tooltip/Tooltip.tsx +50 -25
  61. package/src/components/index.ts +15 -1
  62. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  63. package/src/icons/registry.ts +1 -1
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+
3
+ import React, { useCallback, useMemo, useRef } from 'react';
4
+ import { Image as RNImage, Platform, Pressable, Text, View } from 'react-native';
5
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
+ import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
7
+ import Icon from '../../icons/Icon';
8
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
9
+ const IS_IOS = Platform.OS === 'ios';
10
+ const PRESS_DELAY = IS_IOS ? 130 : 0;
11
+ const DEFAULT_IMAGE_RATIO = 125 / 82;
12
+ const pressedOverlayStyle = {
13
+ opacity: 0.7
14
+ };
15
+
16
+ // Default modes for the "Add" placeholder icon (`iconButton/*` tokens).
17
+ // Caller-supplied `modes` are merged on top so every key here can be overridden.
18
+ const ADD_ICON_DEFAULT_MODES = Object.freeze({
19
+ AppearanceBrand: 'Secondary',
20
+ Emphasis: 'Low',
21
+ "Button / Size": "M"
22
+ });
23
+ const toNumber = (value, fallback) => {
24
+ if (typeof value === 'number') return Number.isFinite(value) ? value : fallback;
25
+ if (typeof value === 'string') {
26
+ const parsed = Number(value);
27
+ return Number.isFinite(parsed) ? parsed : fallback;
28
+ }
29
+ return fallback;
30
+ };
31
+ const toFontWeight = (value, fallback) => {
32
+ if (typeof value === 'number') return String(value);
33
+ if (typeof value === 'string') return value;
34
+ return fallback;
35
+ };
36
+ const normalizeImageSource = src => {
37
+ if (src == null) return undefined;
38
+ if (typeof src === 'string') return {
39
+ uri: src
40
+ };
41
+ return src;
42
+ };
43
+
44
+ /**
45
+ * `AccountCard` — a compact card preview used to represent a linked
46
+ * financial account in lists / grids.
47
+ *
48
+ * Two visual states are supported via the `state` prop:
49
+ *
50
+ * 1. `'connected'` (default): renders a small card-art preview, a bold
51
+ * `title` and a regular-weight `subtitle` (e.g. masked account number).
52
+ * 2. `'add'`: renders a soft-tinted placeholder field with a centred `+`
53
+ * icon and the `title` underneath, intended as the "add new account"
54
+ * entry-point at the end of a list of connected accounts.
55
+ *
56
+ * All values resolve through the `accountCard/*` design tokens with
57
+ * sensible Figma defaults so the card renders correctly out of the box.
58
+ *
59
+ * @component
60
+ * @param {AccountCardProps} props
61
+ */
62
+ function AccountCard({
63
+ state = 'connected',
64
+ title = 'Personal account',
65
+ subtitle = '**** 5651',
66
+ imageSource,
67
+ imageRatio = DEFAULT_IMAGE_RATIO,
68
+ cardSlot,
69
+ addIcon = 'ic_add',
70
+ onPress,
71
+ disabled = false,
72
+ modes = EMPTY_MODES,
73
+ style,
74
+ accessibilityLabel,
75
+ accessibilityHint
76
+ }) {
77
+ const iconModes = useMemo(() => modes === EMPTY_MODES ? ADD_ICON_DEFAULT_MODES : {
78
+ ...ADD_ICON_DEFAULT_MODES,
79
+ ...modes
80
+ }, [modes]);
81
+
82
+ // ---- Tokens ---------------------------------------------------------
83
+ const gap = toNumber(getVariableByName('accountCard/gap', modes), 8);
84
+ const textWrapGap = toNumber(getVariableByName('accountCard/textWrap/gap', modes), 0);
85
+ const titleColor = getVariableByName('accountCard/title/foreground', modes) ?? '#0d0d0f';
86
+ const titleFontFamily = getVariableByName('accountCard/title/fontFamily', modes) ?? 'JioType Var';
87
+ const titleFontSize = toNumber(getVariableByName('accountCard/title/fontSize', modes), 12);
88
+ const titleLineHeight = toNumber(getVariableByName('accountCard/title/lineHeight', modes), 16);
89
+ const titleFontWeight = toFontWeight(getVariableByName('accountCard/title/fontWeight', modes), '700');
90
+ const subtitleColor = getVariableByName('accountCard/subtitle/foreground', modes) ?? '#24262b';
91
+ const subtitleFontFamily = getVariableByName('accountCard/subtitle/fontFamily', modes) ?? 'JioType Var';
92
+ const subtitleFontSize = toNumber(getVariableByName('accountCard/subtitle/fontSize', modes), 12);
93
+ const subtitleLineHeight = toNumber(getVariableByName('accountCard/subtitle/lineHeight', modes), 16);
94
+ const subtitleFontWeight = toFontWeight(getVariableByName('accountCard/subtitle/fontWeight', modes), '400');
95
+ const addFieldRadius = toNumber(getVariableByName('accountCard/addItemField/radius', modes), 8);
96
+ const addFieldBg = getVariableByName('accountCard/addItemField/bg', modes) ?? '#ede8ff';
97
+ const addIconColor = getVariableByName('iconButton/icon/color', iconModes) ?? '#5d00b5';
98
+ const addIconSize = toNumber(getVariableByName('iconButton/icon/size', iconModes), 16);
99
+
100
+ // ---- Styles ---------------------------------------------------------
101
+ const titleStyle = {
102
+ color: titleColor,
103
+ fontFamily: titleFontFamily,
104
+ fontSize: titleFontSize,
105
+ lineHeight: titleLineHeight,
106
+ fontWeight: titleFontWeight,
107
+ width: '100%'
108
+ };
109
+ const subtitleStyle = {
110
+ color: subtitleColor,
111
+ fontFamily: subtitleFontFamily,
112
+ fontSize: subtitleFontSize,
113
+ lineHeight: subtitleLineHeight,
114
+ fontWeight: subtitleFontWeight,
115
+ width: '100%'
116
+ };
117
+ const imageBoxStyle = {
118
+ width: '100%',
119
+ aspectRatio: imageRatio,
120
+ overflow: 'hidden'
121
+ };
122
+
123
+ // RN's `<Image>` accepts `ImageStyle`, which has a narrower `overflow`
124
+ // union than `ViewStyle`. Build the image-only style separately so the
125
+ // shared box dimensions can be reused without a cast.
126
+ const imageStyle = {
127
+ width: '100%',
128
+ aspectRatio: imageRatio
129
+ };
130
+ const addFieldStyle = {
131
+ ...imageBoxStyle,
132
+ backgroundColor: addFieldBg,
133
+ borderRadius: addFieldRadius,
134
+ alignItems: 'center',
135
+ justifyContent: 'center'
136
+ };
137
+
138
+ // ---- Image / placeholder area --------------------------------------
139
+ const renderCardArea = () => {
140
+ if (cardSlot !== undefined && cardSlot !== null) {
141
+ const processed = cloneChildrenWithModes(cardSlot, modes);
142
+ return /*#__PURE__*/_jsx(View, {
143
+ style: imageBoxStyle,
144
+ pointerEvents: "box-none",
145
+ children: processed.length === 1 ? processed[0] : processed
146
+ });
147
+ }
148
+ if (state === 'add') {
149
+ return /*#__PURE__*/_jsx(View, {
150
+ style: addFieldStyle,
151
+ accessibilityElementsHidden: true,
152
+ importantForAccessibility: "no",
153
+ children: /*#__PURE__*/_jsx(Icon, {
154
+ name: addIcon,
155
+ size: addIconSize,
156
+ color: addIconColor
157
+ })
158
+ });
159
+ }
160
+ const normalized = normalizeImageSource(imageSource);
161
+ if (normalized) {
162
+ return /*#__PURE__*/_jsx(RNImage, {
163
+ source: normalized,
164
+ style: imageStyle,
165
+ resizeMode: "cover",
166
+ accessibilityElementsHidden: true,
167
+ importantForAccessibility: "no"
168
+ });
169
+ }
170
+ return /*#__PURE__*/_jsx(View, {
171
+ style: imageBoxStyle
172
+ });
173
+ };
174
+
175
+ // ---- Pressable wiring ----------------------------------------------
176
+ // Keep React state out of the press path. `Pressable`'s style callback
177
+ // applies the pressed visual without a re-render.
178
+ const userHandlersRef = useRef({});
179
+ const handlePressIn = useCallback(e => {
180
+ userHandlersRef.current.onPressIn?.(e);
181
+ }, []);
182
+ const handlePressOut = useCallback(e => {
183
+ userHandlersRef.current.onPressOut?.(e);
184
+ }, []);
185
+ const containerStyle = useMemo(() => ({
186
+ width: '100%',
187
+ flexDirection: 'column',
188
+ alignItems: 'flex-start',
189
+ gap,
190
+ opacity: disabled ? 0.5 : 1
191
+ }), [gap, disabled]);
192
+ const pressableStyle = useCallback(({
193
+ pressed
194
+ }) => [containerStyle, style, pressed && !disabled && onPress ? pressedOverlayStyle : null], [containerStyle, style, disabled, onPress]);
195
+ const showSubtitle = state === 'connected' && subtitle != null && subtitle !== '';
196
+ const a11yRole = onPress ? 'button' : undefined;
197
+ const a11yLabel = accessibilityLabel ?? title;
198
+ const content = /*#__PURE__*/_jsxs(_Fragment, {
199
+ children: [renderCardArea(), title != null && title !== '' || showSubtitle ? /*#__PURE__*/_jsxs(View, {
200
+ style: {
201
+ width: '100%',
202
+ flexDirection: 'column',
203
+ alignItems: 'flex-start',
204
+ gap: textWrapGap
205
+ },
206
+ children: [title != null && title !== '' ? /*#__PURE__*/_jsx(Text, {
207
+ style: titleStyle,
208
+ numberOfLines: 1,
209
+ children: title
210
+ }) : null, showSubtitle ? /*#__PURE__*/_jsx(Text, {
211
+ style: subtitleStyle,
212
+ numberOfLines: 1,
213
+ children: subtitle
214
+ }) : null]
215
+ }) : null]
216
+ });
217
+ if (!onPress) {
218
+ return /*#__PURE__*/_jsx(View, {
219
+ accessibilityLabel: a11yLabel,
220
+ accessibilityHint: accessibilityHint,
221
+ style: [containerStyle, style],
222
+ children: content
223
+ });
224
+ }
225
+ return /*#__PURE__*/_jsx(Pressable, {
226
+ accessibilityRole: a11yRole,
227
+ accessibilityLabel: a11yLabel,
228
+ accessibilityHint: accessibilityHint,
229
+ accessibilityState: {
230
+ disabled
231
+ },
232
+ onPress: disabled ? undefined : onPress,
233
+ disabled: disabled,
234
+ onPressIn: handlePressIn,
235
+ onPressOut: handlePressOut,
236
+ unstable_pressDelay: PRESS_DELAY,
237
+ style: pressableStyle,
238
+ children: content
239
+ });
240
+ }
241
+ export default /*#__PURE__*/React.memo(AccountCard);
@@ -42,12 +42,13 @@ export default function AppBar({
42
42
  const containerStyle = {
43
43
  flexDirection: 'row',
44
44
  alignItems: 'center',
45
- justifyContent: 'space-between',
45
+ // No `justifyContent` here: with the inline middle slot using `flex: 1`
46
+ // the three sections lay out naturally (leading | middle | actions).
47
+ // When middleSlot is absent we fall back to `space-between` at the wrapper
48
+ // level so leading & actions still anchor to the edges.
46
49
  paddingHorizontal: paddingHorizontal ?? 16,
47
50
  paddingVertical: paddingVertical ?? (isMain ? 16 : 10),
48
51
  backgroundColor: backgroundColor ?? '#FFFFFF'
49
- // We can set minHeight if we want to enforce consistency, but padding should dictate it mostly.
50
- // Figma shows specific heights implicitly via padding + content.
51
52
  // MainPage: h=68 (16 top/bot padding? 36 height content?)
52
53
  // SubPage: h=52
53
54
  };
@@ -110,8 +111,17 @@ export default function AppBar({
110
111
  style: actionsStyle,
111
112
  children: cloneChildrenWithModes(React.Children.toArray(actionsSlot), modes)
112
113
  }) : null;
114
+
115
+ // When there is no middleSlot we want leading & actions pinned to the
116
+ // outer edges, so we apply `space-between` at the wrapper. With a middle
117
+ // slot present, the middle (flex: 1) absorbs the remaining space, so
118
+ // `space-between` is a no-op.
119
+ const wrapperStyle = {
120
+ ...containerStyle,
121
+ justifyContent: processedMiddle ? 'flex-start' : 'space-between'
122
+ };
113
123
  return /*#__PURE__*/_jsxs(View, {
114
- style: [containerStyle, style],
124
+ style: [wrapperStyle, style],
115
125
  accessibilityRole: "header",
116
126
  accessibilityLabel: undefined,
117
127
  ...(accessibilityHint ? {
@@ -126,15 +136,11 @@ export default function AppBar({
126
136
  children: processedLeading
127
137
  }), processedMiddle && /*#__PURE__*/_jsx(View, {
128
138
  style: {
129
- position: 'absolute',
130
- left: 0,
131
- right: 0,
132
- top: 0,
133
- bottom: 0,
139
+ flex: 1,
140
+ minWidth: 0,
134
141
  alignItems: 'center',
135
142
  justifyContent: 'center',
136
- zIndex: -1 // Behind actions if overlap? Or should be on top?
137
- // Usually middle title shouldn't block actions. `pointerEvents="box-none"` is safer.
143
+ paddingHorizontal: 8
138
144
  },
139
145
  pointerEvents: "box-none",
140
146
  children: processedMiddle
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- import React from 'react';
3
+ import React, { useMemo } from 'react';
4
4
  import { View, Text } from 'react-native';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
6
  import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
@@ -18,6 +18,14 @@ const DEFAULT_ITEMS = [{
18
18
  label: 'Last updated',
19
19
  value: 'Korem ipsum'
20
20
  }];
21
+
22
+ // Component-level defaults that match the Figma reference. Caller-provided
23
+ // `modes` are merged on top so every key here can be overridden per-instance.
24
+ const DEFAULT_MODES = Object.freeze({
25
+ 'Button / Size': 'S',
26
+ AppearanceBrand: 'Secondary',
27
+ Emphasis: 'Medium'
28
+ });
21
29
  const toNumber = (value, fallback) => {
22
30
  if (typeof value === 'number') return Number.isFinite(value) ? value : fallback;
23
31
  if (typeof value === 'string') {
@@ -56,10 +64,17 @@ function CardBankAccount({
56
64
  buttonLabel = 'Button',
57
65
  onButtonPress,
58
66
  footer,
59
- modes = EMPTY_MODES,
67
+ modes: propModes = EMPTY_MODES,
60
68
  style,
61
69
  accessibilityLabel
62
70
  }) {
71
+ // Merge caller modes on top of `DEFAULT_MODES` so every default key
72
+ // (e.g. `Button / Size`, `AppearanceBrand`, `Emphasis`) can be overridden
73
+ // per-instance while still applying out of the box.
74
+ const modes = useMemo(() => propModes === EMPTY_MODES ? DEFAULT_MODES : {
75
+ ...DEFAULT_MODES,
76
+ ...propModes
77
+ }, [propModes]);
63
78
  const background = getVariableByName('bankAccountCard/background', modes) ?? '#ffffff';
64
79
  const radius = toNumber(getVariableByName('bankAccountCard/radius', modes), 16);
65
80
  const paddingHorizontal = toNumber(getVariableByName('bankAccountCard/padding/horizontal', modes), 12);
@@ -5,12 +5,15 @@ import { View, Text, Pressable } from 'react-native';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
6
  import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
7
7
  import Checkbox from '../Checkbox/Checkbox';
8
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
9
9
  /**
10
10
  * CheckboxItem composes a `Checkbox`, a label and an optional `endSlot` into a
11
11
  * single horizontal pressable row. Pressing anywhere on the row (outside of the
12
12
  * `endSlot`) toggles the checkbox, mirroring the typical native form pattern.
13
13
  *
14
+ * Use the `control` prop to swap the checkbox between the leading (left, default)
15
+ * and trailing (right) edge of the row. The `endSlot` flips to the opposite edge.
16
+ *
14
17
  * Mirrors the Figma "Checkbox Item" component and uses the `checkboxItem/*`
15
18
  * design tokens for typography and spacing.
16
19
  *
@@ -25,6 +28,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
25
28
  * label="Fixed deposit • 0245"
26
29
  * checked={checked}
27
30
  * onValueChange={setChecked}
31
+ * control="leading"
28
32
  * modes={{ 'Color Mode': 'Light' }}
29
33
  * />
30
34
  * ```
@@ -35,6 +39,7 @@ function CheckboxItem({
35
39
  onValueChange,
36
40
  disabled = false,
37
41
  label = 'Fixed deposit • 0245',
42
+ control = 'leading',
38
43
  endSlot,
39
44
  endSlotWidth = 80,
40
45
  modes = EMPTY_MODES,
@@ -42,6 +47,7 @@ function CheckboxItem({
42
47
  labelStyle,
43
48
  accessibilityLabel
44
49
  }) {
50
+ const isTrailing = control === 'trailing';
45
51
  const isControlled = controlledChecked !== undefined;
46
52
  const [internalChecked, setInternalChecked] = useState(defaultChecked);
47
53
  const isChecked = isControlled ? controlledChecked : internalChecked;
@@ -80,7 +86,35 @@ function CheckboxItem({
80
86
  fontWeight: labelFontWeight
81
87
  };
82
88
  const a11yLabel = accessibilityLabel ?? (typeof label === 'string' ? label : undefined);
83
- return /*#__PURE__*/_jsxs(Pressable, {
89
+ const checkboxNode = /*#__PURE__*/_jsx(Checkbox, {
90
+ checked: isChecked,
91
+ disabled: disabled,
92
+ onValueChange: handleToggle,
93
+ modes: modes,
94
+ ...(a11yLabel !== undefined ? {
95
+ accessibilityLabel: a11yLabel
96
+ } : {})
97
+ });
98
+ const labelNode = label != null && label !== false ? typeof label === 'string' || typeof label === 'number' ? /*#__PURE__*/_jsx(Text, {
99
+ style: [resolvedLabelStyle, labelStyle],
100
+ selectable: false,
101
+ children: label
102
+ }) : /*#__PURE__*/_jsx(View, {
103
+ style: {
104
+ flex: 1,
105
+ minWidth: 0
106
+ },
107
+ children: label
108
+ }) : null;
109
+ const endSlotNode = endSlot ? /*#__PURE__*/_jsx(View, {
110
+ style: {
111
+ width: endSlotWidth,
112
+ flexShrink: 0,
113
+ alignItems: 'stretch'
114
+ },
115
+ children: cloneChildrenWithModes(endSlot, modes)
116
+ }) : null;
117
+ return /*#__PURE__*/_jsx(Pressable, {
84
118
  style: [containerStyle, style],
85
119
  onPress: handleToggle,
86
120
  disabled: disabled,
@@ -90,30 +124,11 @@ function CheckboxItem({
90
124
  disabled
91
125
  },
92
126
  accessibilityLabel: a11yLabel,
93
- children: [/*#__PURE__*/_jsx(Checkbox, {
94
- checked: isChecked,
95
- disabled: disabled,
96
- onValueChange: handleToggle,
97
- modes: modes,
98
- accessibilityLabel: a11yLabel
99
- }), label != null && label !== false ? typeof label === 'string' || typeof label === 'number' ? /*#__PURE__*/_jsx(Text, {
100
- style: [resolvedLabelStyle, labelStyle],
101
- selectable: false,
102
- children: label
103
- }) : /*#__PURE__*/_jsx(View, {
104
- style: {
105
- flex: 1,
106
- minWidth: 0
107
- },
108
- children: label
109
- }) : null, endSlot ? /*#__PURE__*/_jsx(View, {
110
- style: {
111
- width: endSlotWidth,
112
- flexShrink: 0,
113
- alignItems: 'stretch'
114
- },
115
- children: cloneChildrenWithModes(endSlot, modes)
116
- }) : null]
127
+ children: isTrailing ? /*#__PURE__*/_jsxs(_Fragment, {
128
+ children: [endSlotNode, labelNode, checkboxNode]
129
+ }) : /*#__PURE__*/_jsxs(_Fragment, {
130
+ children: [checkboxNode, labelNode, endSlotNode]
131
+ })
117
132
  });
118
133
  }
119
134
  export default CheckboxItem;
@@ -0,0 +1,206 @@
1
+ "use strict";
2
+
3
+ import React, { useCallback, useMemo, useState } from 'react';
4
+ import { Platform, Pressable, ScrollView, Text, View } from 'react-native';
5
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
+ import { useTokens } from '../../design-tokens/JFSThemeProvider';
7
+ import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
8
+ import Icon from '../../icons/Icon';
9
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
+ const IS_WEB = Platform.OS === 'web';
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // DropdownItem
14
+ // ---------------------------------------------------------------------------
15
+
16
+ function useDropdownItemTokens(modes) {
17
+ return useMemo(() => {
18
+ // The `dropdownItem/background` token aliases through the
19
+ // `Dropdown Item State` collection (Idle | Selected), so we resolve
20
+ // both possibilities up-front and pick at render time.
21
+ const idleBackground = getVariableByName('dropdownItem/background', {
22
+ ...modes,
23
+ 'Dropdown Item State': 'Idle'
24
+ }) || '#ffffff';
25
+ const selectedBackground = getVariableByName('dropdownItem/background', {
26
+ ...modes,
27
+ 'Dropdown Item State': 'Selected'
28
+ }) || '#f5f5f5';
29
+ const foreground = getVariableByName('dropdownItem/foreground', modes) || '#000000';
30
+ const fontFamily = getVariableByName('dropdownItem/fontFamily', modes) || 'JioType Var';
31
+ const fontSize = parseInt(getVariableByName('dropdownItem/fontSize', modes), 10) || 16;
32
+ const fontWeight = getVariableByName('dropdownItem/fontWeight', modes) || '400';
33
+ const lineHeight = parseInt(getVariableByName('dropdownItem/lineHeight', modes), 10) || 19;
34
+ const gap = parseInt(getVariableByName('dropdownItem/gap', modes), 10) || 8;
35
+ const paddingHorizontal = parseInt(getVariableByName('dropdownItem/padding/horizontal', modes), 10) || 12;
36
+ const paddingVertical = parseInt(getVariableByName('dropdownItem/padding/vertical', modes), 10) || 12;
37
+ return {
38
+ idleBackground,
39
+ selectedBackground,
40
+ foreground,
41
+ fontFamily,
42
+ fontSize,
43
+ fontWeight,
44
+ lineHeight,
45
+ gap,
46
+ paddingHorizontal,
47
+ paddingVertical
48
+ };
49
+ }, [modes]);
50
+ }
51
+ export function DropdownItem({
52
+ label,
53
+ value = null,
54
+ selected = false,
55
+ disabled = false,
56
+ leading,
57
+ trailing,
58
+ onPress,
59
+ children,
60
+ modes: propModes = EMPTY_MODES,
61
+ style,
62
+ labelStyle,
63
+ accessibilityLabel,
64
+ accessibilityHint
65
+ }) {
66
+ const {
67
+ modes: globalModes
68
+ } = useTokens();
69
+ const modes = useMemo(() => ({
70
+ ...globalModes,
71
+ ...propModes
72
+ }), [globalModes, propModes]);
73
+ const tokens = useDropdownItemTokens(modes);
74
+ const [isHovered, setIsHovered] = useState(false);
75
+ const handlePress = useCallback(() => {
76
+ if (disabled) return;
77
+ onPress?.(value);
78
+ }, [disabled, onPress, value]);
79
+ const containerStyle = useCallback(({
80
+ pressed
81
+ }) => {
82
+ const showSelected = pressed || isHovered && IS_WEB || selected;
83
+ const base = {
84
+ flexDirection: 'row',
85
+ alignItems: 'center',
86
+ gap: tokens.gap,
87
+ paddingHorizontal: tokens.paddingHorizontal,
88
+ paddingVertical: tokens.paddingVertical,
89
+ backgroundColor: showSelected ? tokens.selectedBackground : tokens.idleBackground,
90
+ opacity: disabled ? 0.4 : 1,
91
+ width: '100%'
92
+ };
93
+ return [base, style];
94
+ }, [tokens.gap, tokens.paddingHorizontal, tokens.paddingVertical, tokens.idleBackground, tokens.selectedBackground, isHovered, selected, disabled, style]);
95
+ const textStyle = {
96
+ color: tokens.foreground,
97
+ fontFamily: tokens.fontFamily,
98
+ fontSize: tokens.fontSize,
99
+ fontWeight: tokens.fontWeight,
100
+ lineHeight: tokens.lineHeight,
101
+ flexShrink: 1
102
+ };
103
+ const processedLeading = leading ? cloneChildrenWithModes(React.Children.toArray(leading), modes) : null;
104
+ const customTrailing = trailing ? cloneChildrenWithModes(React.Children.toArray(trailing), modes) : null;
105
+ const showDefaultCheck = !trailing && selected;
106
+ const fallbackA11yLabel = accessibilityLabel || (typeof label === 'string' ? label : 'Dropdown item');
107
+ const a11yProps = {
108
+ accessibilityRole: 'menuitem',
109
+ accessibilityLabel: fallbackA11yLabel,
110
+ accessibilityState: {
111
+ selected,
112
+ disabled
113
+ }
114
+ };
115
+ if (accessibilityHint) {
116
+ a11yProps.accessibilityHint = accessibilityHint;
117
+ }
118
+ const handleHoverIn = useCallback(() => {
119
+ if (IS_WEB && !disabled) setIsHovered(true);
120
+ }, [disabled]);
121
+ const handleHoverOut = useCallback(() => {
122
+ if (IS_WEB) setIsHovered(false);
123
+ }, []);
124
+ return /*#__PURE__*/_jsxs(Pressable, {
125
+ onPress: handlePress,
126
+ disabled: disabled,
127
+ onHoverIn: handleHoverIn,
128
+ onHoverOut: handleHoverOut,
129
+ style: containerStyle,
130
+ ...a11yProps,
131
+ children: [processedLeading, children != null ? children : /*#__PURE__*/_jsx(Text, {
132
+ style: [textStyle, labelStyle],
133
+ numberOfLines: 1,
134
+ children: label
135
+ }), customTrailing, showDefaultCheck && /*#__PURE__*/_jsx(Icon, {
136
+ name: "ic_confirm",
137
+ size: 16,
138
+ color: tokens.foreground
139
+ })]
140
+ });
141
+ }
142
+
143
+ // ---------------------------------------------------------------------------
144
+ // Dropdown (popup surface)
145
+ // ---------------------------------------------------------------------------
146
+
147
+ /**
148
+ * `Dropdown` is the visual surface (popup) that contains a list of
149
+ * `DropdownItem`s. It is responsible for the background, rounded corners,
150
+ * elevation/shadow, and clipping. Use it standalone for menu UIs, or rely on
151
+ * `DropdownInput` which composes it into a form-field experience.
152
+ */
153
+ export function Dropdown({
154
+ children,
155
+ maxHeight,
156
+ modes: propModes = EMPTY_MODES,
157
+ style,
158
+ accessibilityLabel
159
+ }) {
160
+ const {
161
+ modes: globalModes
162
+ } = useTokens();
163
+ const modes = useMemo(() => ({
164
+ ...globalModes,
165
+ ...propModes
166
+ }), [globalModes, propModes]);
167
+ const radius = parseInt(getVariableByName('dropdown/radius', modes), 10) || 8;
168
+ const background = getVariableByName('dropdown/background', modes) || '#ffffff';
169
+ const shadowColor = getVariableByName('dropdown/shadow/color', modes) || 'rgba(0, 0, 0, 0.08)';
170
+ const shadowOffsetX = parseInt(getVariableByName('dropdown/shadow/offsetX', modes), 10) || 0;
171
+ const shadowOffsetY = parseInt(getVariableByName('dropdown/shadow/offsetY', modes), 10) || 4;
172
+ const shadowBlur = parseInt(getVariableByName('dropdown/shadow/blur', modes), 10) || 16;
173
+ const containerStyle = {
174
+ backgroundColor: background,
175
+ borderRadius: radius,
176
+ overflow: 'hidden',
177
+ shadowColor,
178
+ shadowOffset: {
179
+ width: shadowOffsetX,
180
+ height: shadowOffsetY
181
+ },
182
+ shadowOpacity: 1,
183
+ shadowRadius: shadowBlur / 2,
184
+ elevation: 4
185
+ };
186
+ const content = /*#__PURE__*/_jsx(View, {
187
+ style: {
188
+ flexDirection: 'column'
189
+ },
190
+ children: cloneChildrenWithModes(children, modes)
191
+ });
192
+ return /*#__PURE__*/_jsx(View, {
193
+ style: [containerStyle, style],
194
+ accessibilityRole: "menu",
195
+ accessibilityLabel: accessibilityLabel || 'Dropdown menu',
196
+ children: maxHeight != null ? /*#__PURE__*/_jsx(ScrollView, {
197
+ style: {
198
+ maxHeight
199
+ },
200
+ showsVerticalScrollIndicator: true,
201
+ keyboardShouldPersistTaps: "handled",
202
+ children: content
203
+ }) : content
204
+ });
205
+ }
206
+ export default Dropdown;