jfs-components 0.0.73 → 0.0.77

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 (134) hide show
  1. package/CHANGELOG.md +115 -6
  2. package/lib/commonjs/components/AccountCard/AccountCard.js +247 -0
  3. package/lib/commonjs/components/ActionFooter/ActionFooter.js +147 -82
  4. package/lib/commonjs/components/AppBar/AppBar.js +17 -11
  5. package/lib/commonjs/components/Avatar/Avatar.js +20 -0
  6. package/lib/commonjs/components/Badge/Badge.js +23 -0
  7. package/lib/commonjs/components/Button/Button.js +37 -0
  8. package/lib/commonjs/components/CardBankAccount/CardBankAccount.js +18 -2
  9. package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +40 -25
  10. package/lib/commonjs/components/Dropdown/Dropdown.js +214 -0
  11. package/lib/commonjs/components/DropdownInput/DropdownInput.js +542 -0
  12. package/lib/commonjs/components/FormField/FormField.js +328 -178
  13. package/lib/commonjs/components/IconButton/IconButton.js +20 -0
  14. package/lib/commonjs/components/Image/Image.js +26 -1
  15. package/lib/commonjs/components/LottieIntroBlock/LottieIntroBlock.js +150 -0
  16. package/lib/commonjs/components/LottiePlayer/LottiePlayer.js +116 -0
  17. package/lib/commonjs/components/LottiePlayer/LottiePlayer.web.js +82 -0
  18. package/lib/commonjs/components/LottiePlayer/loadNativeLottieView.js +74 -0
  19. package/lib/commonjs/components/LottiePlayer/loadWebLottieView.js +50 -0
  20. package/lib/commonjs/components/PageHero/PageHero.js +189 -0
  21. package/lib/commonjs/components/PoweredByLabel/PoweredByLabel.js +135 -0
  22. package/lib/commonjs/components/PoweredByLabel/finvu.png +0 -0
  23. package/lib/commonjs/components/RechargeCard/RechargeCard.js +32 -17
  24. package/lib/commonjs/components/Text/Text.js +40 -3
  25. package/lib/commonjs/components/Tooltip/Tooltip.js +34 -27
  26. package/lib/commonjs/components/index.js +67 -0
  27. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  28. package/lib/commonjs/icons/Icon.js +16 -0
  29. package/lib/commonjs/icons/registry.js +1 -1
  30. package/lib/commonjs/index.js +12 -0
  31. package/lib/commonjs/skeleton/Skeleton.js +234 -0
  32. package/lib/commonjs/skeleton/SkeletonGroup.js +140 -0
  33. package/lib/commonjs/skeleton/index.js +58 -0
  34. package/lib/commonjs/skeleton/shimmer-tokens.js +189 -0
  35. package/lib/commonjs/skeleton/useReducedMotion.js +64 -0
  36. package/lib/module/components/AccountCard/AccountCard.js +241 -0
  37. package/lib/module/components/ActionFooter/ActionFooter.js +146 -82
  38. package/lib/module/components/AppBar/AppBar.js +17 -11
  39. package/lib/module/components/Avatar/Avatar.js +19 -0
  40. package/lib/module/components/Badge/Badge.js +23 -0
  41. package/lib/module/components/Button/Button.js +37 -0
  42. package/lib/module/components/CardBankAccount/CardBankAccount.js +17 -2
  43. package/lib/module/components/CheckboxItem/CheckboxItem.js +41 -26
  44. package/lib/module/components/Dropdown/Dropdown.js +206 -0
  45. package/lib/module/components/DropdownInput/DropdownInput.js +536 -0
  46. package/lib/module/components/FormField/FormField.js +330 -180
  47. package/lib/module/components/IconButton/IconButton.js +20 -0
  48. package/lib/module/components/Image/Image.js +25 -1
  49. package/lib/module/components/LottieIntroBlock/LottieIntroBlock.js +144 -0
  50. package/lib/module/components/LottiePlayer/LottiePlayer.js +111 -0
  51. package/lib/module/components/LottiePlayer/LottiePlayer.web.js +77 -0
  52. package/lib/module/components/LottiePlayer/loadNativeLottieView.js +69 -0
  53. package/lib/module/components/LottiePlayer/loadWebLottieView.js +45 -0
  54. package/lib/module/components/PageHero/PageHero.js +183 -0
  55. package/lib/module/components/PoweredByLabel/PoweredByLabel.js +130 -0
  56. package/lib/module/components/PoweredByLabel/finvu.png +0 -0
  57. package/lib/module/components/RechargeCard/RechargeCard.js +33 -17
  58. package/lib/module/components/Text/Text.js +40 -3
  59. package/lib/module/components/Tooltip/Tooltip.js +34 -27
  60. package/lib/module/components/index.js +8 -1
  61. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  62. package/lib/module/icons/Icon.js +16 -0
  63. package/lib/module/icons/registry.js +1 -1
  64. package/lib/module/index.js +2 -1
  65. package/lib/module/skeleton/Skeleton.js +229 -0
  66. package/lib/module/skeleton/SkeletonGroup.js +133 -0
  67. package/lib/module/skeleton/index.js +6 -0
  68. package/lib/module/skeleton/shimmer-tokens.js +181 -0
  69. package/lib/module/skeleton/useReducedMotion.js +61 -0
  70. package/lib/typescript/src/components/AccountCard/AccountCard.d.ts +81 -0
  71. package/lib/typescript/src/components/ActionFooter/ActionFooter.d.ts +26 -21
  72. package/lib/typescript/src/components/Avatar/Avatar.d.ts +7 -1
  73. package/lib/typescript/src/components/Badge/Badge.d.ts +7 -1
  74. package/lib/typescript/src/components/Button/Button.d.ts +8 -1
  75. package/lib/typescript/src/components/CardBankAccount/CardBankAccount.d.ts +9 -2
  76. package/lib/typescript/src/components/CheckboxItem/CheckboxItem.d.ts +18 -2
  77. package/lib/typescript/src/components/Dropdown/Dropdown.d.ts +62 -0
  78. package/lib/typescript/src/components/DropdownInput/DropdownInput.d.ts +107 -0
  79. package/lib/typescript/src/components/FormField/FormField.d.ts +76 -19
  80. package/lib/typescript/src/components/IconButton/IconButton.d.ts +7 -1
  81. package/lib/typescript/src/components/Image/Image.d.ts +8 -1
  82. package/lib/typescript/src/components/LottieIntroBlock/LottieIntroBlock.d.ts +58 -0
  83. package/lib/typescript/src/components/LottiePlayer/LottiePlayer.d.ts +85 -0
  84. package/lib/typescript/src/components/LottiePlayer/LottiePlayer.web.d.ts +28 -0
  85. package/lib/typescript/src/components/LottiePlayer/loadNativeLottieView.d.ts +11 -0
  86. package/lib/typescript/src/components/LottiePlayer/loadWebLottieView.d.ts +11 -0
  87. package/lib/typescript/src/components/PageHero/PageHero.d.ts +79 -0
  88. package/lib/typescript/src/components/PoweredByLabel/PoweredByLabel.d.ts +70 -0
  89. package/lib/typescript/src/components/Text/Text.d.ts +31 -2
  90. package/lib/typescript/src/components/Tooltip/Tooltip.d.ts +13 -2
  91. package/lib/typescript/src/components/index.d.ts +8 -1
  92. package/lib/typescript/src/icons/Icon.d.ts +7 -1
  93. package/lib/typescript/src/icons/registry.d.ts +1 -1
  94. package/lib/typescript/src/index.d.ts +1 -0
  95. package/lib/typescript/src/skeleton/Skeleton.d.ts +60 -0
  96. package/lib/typescript/src/skeleton/SkeletonGroup.d.ts +78 -0
  97. package/lib/typescript/src/skeleton/index.d.ts +5 -0
  98. package/lib/typescript/src/skeleton/shimmer-tokens.d.ts +160 -0
  99. package/lib/typescript/src/skeleton/useReducedMotion.d.ts +15 -0
  100. package/package.json +11 -3
  101. package/src/components/AccountCard/AccountCard.tsx +376 -0
  102. package/src/components/ActionFooter/ActionFooter.tsx +152 -86
  103. package/src/components/AppBar/AppBar.tsx +25 -14
  104. package/src/components/Avatar/Avatar.tsx +26 -0
  105. package/src/components/Badge/Badge.tsx +27 -0
  106. package/src/components/Button/Button.tsx +40 -0
  107. package/src/components/CardBankAccount/CardBankAccount.tsx +29 -3
  108. package/src/components/CheckboxItem/CheckboxItem.tsx +65 -30
  109. package/src/components/Dropdown/Dropdown.tsx +331 -0
  110. package/src/components/DropdownInput/DropdownInput.tsx +819 -0
  111. package/src/components/FormField/FormField.tsx +542 -215
  112. package/src/components/IconButton/IconButton.tsx +27 -0
  113. package/src/components/Image/Image.tsx +25 -0
  114. package/src/components/LottieIntroBlock/LottieIntroBlock.tsx +202 -0
  115. package/src/components/LottiePlayer/LottiePlayer.tsx +145 -0
  116. package/src/components/LottiePlayer/LottiePlayer.web.tsx +94 -0
  117. package/src/components/LottiePlayer/loadNativeLottieView.tsx +87 -0
  118. package/src/components/LottiePlayer/loadWebLottieView.tsx +64 -0
  119. package/src/components/PageHero/PageHero.tsx +257 -0
  120. package/src/components/PoweredByLabel/PoweredByLabel.tsx +221 -0
  121. package/src/components/PoweredByLabel/finvu.png +0 -0
  122. package/src/components/RechargeCard/RechargeCard.tsx +32 -24
  123. package/src/components/Text/Text.tsx +78 -3
  124. package/src/components/Tooltip/Tooltip.tsx +50 -25
  125. package/src/components/index.ts +16 -1
  126. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  127. package/src/icons/Icon.tsx +17 -0
  128. package/src/icons/registry.ts +1 -1
  129. package/src/index.ts +1 -0
  130. package/src/skeleton/Skeleton.tsx +298 -0
  131. package/src/skeleton/SkeletonGroup.tsx +193 -0
  132. package/src/skeleton/index.ts +10 -0
  133. package/src/skeleton/shimmer-tokens.ts +221 -0
  134. package/src/skeleton/useReducedMotion.ts +72 -0
@@ -4,6 +4,8 @@ import React from 'react';
4
4
  import { View, Text, Pressable } from 'react-native';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
6
  import { EMPTY_MODES } from '../../utils/react-utils';
7
+ import Skeleton from '../../skeleton/Skeleton';
8
+ import { useSkeleton } from '../../skeleton/SkeletonGroup';
7
9
  import { jsx as _jsx } from "react/jsx-runtime";
8
10
  function Badge({
9
11
  label = 'Label',
@@ -12,6 +14,7 @@ function Badge({
12
14
  accessibilityLabel,
13
15
  style,
14
16
  labelStyle,
17
+ loading,
15
18
  ...rest
16
19
  }) {
17
20
  // Resolve token values (fall back to sensible defaults)
@@ -24,6 +27,26 @@ function Badge({
24
27
  const paddingVertical = Number(getVariableByName('badge/padding/vertical', modes)) || 4;
25
28
  const borderRadius = Number(getVariableByName('badge/radius', modes)) || 4;
26
29
  const lineHeight = Number(getVariableByName('badge/label/lineHeight', modes)) || Math.round(fontSize * 1.2);
30
+
31
+ // Skeleton short-circuit. Size derived from the same tokens the loaded
32
+ // badge would use so the placeholder occupies the same box.
33
+ const {
34
+ active: groupActive
35
+ } = useSkeleton();
36
+ const isLoading = loading ?? groupActive;
37
+ if (isLoading) {
38
+ const charWidth = fontSize * 0.55;
39
+ const labelWidth = Math.max(label.length, 3) * charWidth;
40
+ return /*#__PURE__*/_jsx(Skeleton, {
41
+ kind: "badge",
42
+ width: paddingHorizontal * 2 + labelWidth,
43
+ height: paddingVertical * 2 + lineHeight,
44
+ style: {
45
+ alignSelf: 'flex-start'
46
+ },
47
+ modes: modes
48
+ });
49
+ }
27
50
  const Container = onPress ? Pressable : View;
28
51
  const containerStyle = {
29
52
  backgroundColor,
@@ -6,6 +6,8 @@ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
6
6
  import { usePressableWebSupport } from '../../utils/web-platform-utils';
7
7
  import { EMPTY_MODES } from '../../utils/react-utils';
8
8
  import Icon from '../../icons/Icon';
9
+ import Skeleton from '../../skeleton/Skeleton';
10
+ import { useSkeleton } from '../../skeleton/SkeletonGroup';
9
11
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
12
  // ---------------------------------------------------------------------------
11
13
  // Module-scope constants — never re-allocated per render.
@@ -167,6 +169,7 @@ function ButtonImpl({
167
169
  accessibilityHint,
168
170
  accessibilityState,
169
171
  webAccessibilityProps,
172
+ loading,
170
173
  ...rest
171
174
  }) {
172
175
  // Hover state is web-only in practice; the setter is gated so native taps
@@ -182,6 +185,14 @@ function ButtonImpl({
182
185
  userHandlersRef.current.onHoverOut = rest?.onHoverOut;
183
186
  const tokens = useMemo(() => resolveButtonTokens(modes, disabled), [modes, disabled]);
184
187
 
188
+ // Skeleton context — read unconditionally so React's hook order stays
189
+ // stable. The actual short-circuit return happens AFTER all remaining
190
+ // hooks have been called below.
191
+ const {
192
+ active: groupActive
193
+ } = useSkeleton();
194
+ const isLoading = loading ?? groupActive;
195
+
185
196
  // Active label color: base by default; hover override (web-only) when hovered.
186
197
  // Press color is intentionally NOT applied to the label on native — applying
187
198
  // it would require a React render per touch and re-introduce the flicker.
@@ -259,6 +270,32 @@ function ButtonImpl({
259
270
  console.warn('[Button] Custom content is used without an explicit `accessibilityLabel` or string `label`. ' + 'Screen readers may not announce this button correctly.');
260
271
  }
261
272
  }
273
+ if (isLoading) {
274
+ const {
275
+ container,
276
+ baseLabel,
277
+ iconSize,
278
+ accessoryOffset
279
+ } = tokens;
280
+ const paddingHorizontal = container.paddingHorizontal ?? 20;
281
+ const paddingVertical = container.paddingVertical ?? 12;
282
+ const lineHeight = baseLabel.lineHeight ?? 19;
283
+ const fontSize = baseLabel.fontSize ?? 16;
284
+ const labelText = typeof label === 'string' ? label : 'Button';
285
+ const charWidth = fontSize * 0.55;
286
+ const labelWidth = Math.max(labelText.length, 4) * charWidth;
287
+ const hasAccessory = !!(leading || trailing || icon);
288
+ const accessoryWidth = hasAccessory ? iconSize + accessoryOffset * 2 : 0;
289
+ const skeletonWidth = paddingHorizontal * 2 + labelWidth + accessoryWidth;
290
+ const skeletonHeight = paddingVertical * 2 + lineHeight;
291
+ return /*#__PURE__*/_jsx(Skeleton, {
292
+ kind: "other",
293
+ width: skeletonWidth,
294
+ height: skeletonHeight,
295
+ style: style,
296
+ modes: modes
297
+ });
298
+ }
262
299
  return /*#__PURE__*/_jsxs(Pressable, {
263
300
  accessibilityRole: "button",
264
301
  accessibilityLabel: undefined,
@@ -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;