jp-composter 0.1.0

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 (194) hide show
  1. package/dist/index.d.mts +997 -0
  2. package/dist/index.d.ts +997 -0
  3. package/dist/index.js +36837 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/index.mjs +36778 -0
  6. package/dist/index.mjs.map +1 -0
  7. package/package.json +66 -0
  8. package/src/SliceUI/IconMoon.tsx +33 -0
  9. package/src/SliceUI/assets/Anatomy diagram copy.svg +19 -0
  10. package/src/SliceUI/assets/Anatomy diagram.svg +19 -0
  11. package/src/SliceUI/assets/Anatomycheck.svg +15 -0
  12. package/src/SliceUI/assets/Anatomyinput.svg +32 -0
  13. package/src/SliceUI/assets/Checkbox.jpg +0 -0
  14. package/src/SliceUI/assets/Diagram copy.svg +15 -0
  15. package/src/SliceUI/assets/Diagram.jpg +0 -0
  16. package/src/SliceUI/assets/Diagram.svg +15 -0
  17. package/src/SliceUI/assets/Frame 5 copy.png +0 -0
  18. package/src/SliceUI/assets/Frame 5.png +0 -0
  19. package/src/SliceUI/assets/Frame 65.png +0 -0
  20. package/src/SliceUI/assets/Frame_65.png +0 -0
  21. package/src/SliceUI/assets/Icon copy.svg +3 -0
  22. package/src/SliceUI/assets/Icon.svg +3 -0
  23. package/src/SliceUI/assets/Icon_Bridging copy.svg +39 -0
  24. package/src/SliceUI/assets/Icon_Bridging.svg +39 -0
  25. package/src/SliceUI/assets/Icon_Consistent copy.svg +39 -0
  26. package/src/SliceUI/assets/Icon_Consistent.svg +39 -0
  27. package/src/SliceUI/assets/Icon_Plug copy.svg +38 -0
  28. package/src/SliceUI/assets/Icon_Plug.svg +38 -0
  29. package/src/SliceUI/assets/Icon_Reusable copy.svg +39 -0
  30. package/src/SliceUI/assets/Icon_Reusable.svg +39 -0
  31. package/src/SliceUI/assets/Layer_1.png +0 -0
  32. package/src/SliceUI/assets/accessibility.png +0 -0
  33. package/src/SliceUI/assets/accessibility.svg +1 -0
  34. package/src/SliceUI/assets/addon-library.png +0 -0
  35. package/src/SliceUI/assets/assets.png +0 -0
  36. package/src/SliceUI/assets/avif-test-image.avif +0 -0
  37. package/src/SliceUI/assets/bridging.svg +13 -0
  38. package/src/SliceUI/assets/consistent.svg +11 -0
  39. package/src/SliceUI/assets/context.png +0 -0
  40. package/src/SliceUI/assets/discord.svg +1 -0
  41. package/src/SliceUI/assets/docs.png +0 -0
  42. package/src/SliceUI/assets/figma-plugin.png +0 -0
  43. package/src/SliceUI/assets/github.svg +1 -0
  44. package/src/SliceUI/assets/resources/Anatomy diagram.svg +19 -0
  45. package/src/SliceUI/assets/resources/Anatomycheck.svg +15 -0
  46. package/src/SliceUI/assets/resources/Anatomyinput.svg +32 -0
  47. package/src/SliceUI/assets/resources/Diagram.svg +15 -0
  48. package/src/SliceUI/assets/resources/Frame 5.png +0 -0
  49. package/src/SliceUI/assets/resources/Frame 65.png +0 -0
  50. package/src/SliceUI/assets/resources/Icon.svg +3 -0
  51. package/src/SliceUI/assets/resources/Icon_Bridging.svg +39 -0
  52. package/src/SliceUI/assets/resources/Icon_Consistent.svg +39 -0
  53. package/src/SliceUI/assets/resources/Icon_Plug.svg +38 -0
  54. package/src/SliceUI/assets/resources/Icon_Reusable.svg +39 -0
  55. package/src/SliceUI/assets/resources/fonts/FontIcon.json +150 -0
  56. package/src/SliceUI/assets/resources/fonts/Lato-Black.ttf +0 -0
  57. package/src/SliceUI/assets/resources/fonts/Lato-Bold.ttf +0 -0
  58. package/src/SliceUI/assets/resources/fonts/Lato-Heavy.ttf +0 -0
  59. package/src/SliceUI/assets/resources/fonts/Lato-Medium.ttf +0 -0
  60. package/src/SliceUI/assets/resources/fonts/Lato-Regular.ttf +0 -0
  61. package/src/SliceUI/assets/resources/fonts/Lato.woff2 +0 -0
  62. package/src/SliceUI/assets/resources/fonts/icomoon.eot +0 -0
  63. package/src/SliceUI/assets/resources/fonts/icomoon.svg +601 -0
  64. package/src/SliceUI/assets/resources/fonts/icomoon.ttf +0 -0
  65. package/src/SliceUI/assets/resources/fonts/icomoon.woff +0 -0
  66. package/src/SliceUI/assets/resources/fonts/selection.json +1 -0
  67. package/src/SliceUI/assets/share.png +0 -0
  68. package/src/SliceUI/assets/styling.png +0 -0
  69. package/src/SliceUI/assets/testing.png +0 -0
  70. package/src/SliceUI/assets/theming.png +0 -0
  71. package/src/SliceUI/assets/tutorials.svg +1 -0
  72. package/src/SliceUI/assets/youtube.svg +1 -0
  73. package/src/SliceUI/automation/helper.ts +29 -0
  74. package/src/SliceUI/avatar/Avatar.tsx +237 -0
  75. package/src/SliceUI/avatar/Token.ts +116 -0
  76. package/src/SliceUI/avatar/Type.ts +36 -0
  77. package/src/SliceUI/avatar/helper.ts +53 -0
  78. package/src/SliceUI/badge/Badge.tsx +308 -0
  79. package/src/SliceUI/badge/Token.ts +202 -0
  80. package/src/SliceUI/badge/Type.ts +46 -0
  81. package/src/SliceUI/badge/helper.ts +39 -0
  82. package/src/SliceUI/button/Button.tsx +243 -0
  83. package/src/SliceUI/button/Token.ts +138 -0
  84. package/src/SliceUI/button/Type.ts +34 -0
  85. package/src/SliceUI/button/helper.ts +125 -0
  86. package/src/SliceUI/checkbox/Checkbox.tsx +176 -0
  87. package/src/SliceUI/checkbox/Token.ts +128 -0
  88. package/src/SliceUI/checkbox/Type.ts +35 -0
  89. package/src/SliceUI/chip/Chip.tsx +290 -0
  90. package/src/SliceUI/chip/Token.ts +151 -0
  91. package/src/SliceUI/chip/Type.ts +43 -0
  92. package/src/SliceUI/chip/helper.ts +40 -0
  93. package/src/SliceUI/colors/Pallete.ts +151 -0
  94. package/src/SliceUI/colors/Token.ts +110 -0
  95. package/src/SliceUI/colors/Type.ts +56 -0
  96. package/src/SliceUI/contextProvider/context.tsx +108 -0
  97. package/src/SliceUI/divider/Divider.tsx +109 -0
  98. package/src/SliceUI/divider/Token.ts +18 -0
  99. package/src/SliceUI/divider/Type.ts +26 -0
  100. package/src/SliceUI/icon/CustomIcon.ts +4 -0
  101. package/src/SliceUI/icon/IcoMoonIcon.tsx +11 -0
  102. package/src/SliceUI/icon/Icon.tsx +38 -0
  103. package/src/SliceUI/icon/Token.ts +14 -0
  104. package/src/SliceUI/icon/Type.ts +13 -0
  105. package/src/SliceUI/icon/selection.json +1 -0
  106. package/src/SliceUI/input/Input.tsx +573 -0
  107. package/src/SliceUI/input/ToDo.md +99 -0
  108. package/src/SliceUI/input/Token.ts +372 -0
  109. package/src/SliceUI/input/Type.ts +109 -0
  110. package/src/SliceUI/input/components/InputPortal.tsx +211 -0
  111. package/src/SliceUI/input/components/NativeBottomSheet.tsx +296 -0
  112. package/src/SliceUI/input/components/SelectChip.tsx +185 -0
  113. package/src/SliceUI/input/components/SelectList.tsx +173 -0
  114. package/src/SliceUI/input/components/SelectListItem.tsx +377 -0
  115. package/src/SliceUI/input/components/SelectScrollbarStyle.ts +44 -0
  116. package/src/SliceUI/input/hooks/useCustomScrollbar.ts +17 -0
  117. package/src/SliceUI/input/hooks/useInputState.ts +41 -0
  118. package/src/SliceUI/input/hooks/useLabelAnimation.ts +132 -0
  119. package/src/SliceUI/input/hooks/useOutsideClick.ts +38 -0
  120. package/src/SliceUI/input/hooks/useSelectLogic.ts +338 -0
  121. package/src/SliceUI/input/utils/inputUtils.ts +120 -0
  122. package/src/SliceUI/input/utils/selectUtils.ts +85 -0
  123. package/src/SliceUI/input/utils/styleUtils.ts +50 -0
  124. package/src/SliceUI/input/variants/CurrencyInput/CurrencyInput.tsx +16 -0
  125. package/src/SliceUI/input/variants/CurrencyInput/NativeCurrencyInput.tsx +181 -0
  126. package/src/SliceUI/input/variants/CurrencyInput/WebCurrencyInput.tsx +163 -0
  127. package/src/SliceUI/input/variants/CurrencyInput/types.ts +17 -0
  128. package/src/SliceUI/input/variants/PhoneInput/NativePhoneInput.tsx +189 -0
  129. package/src/SliceUI/input/variants/PhoneInput/PhoneInput.tsx +16 -0
  130. package/src/SliceUI/input/variants/PhoneInput/WebPhoneInput.tsx +291 -0
  131. package/src/SliceUI/input/variants/PhoneInput/types.ts +22 -0
  132. package/src/SliceUI/input/variants/SelectInput/SelectInput.tsx +407 -0
  133. package/src/SliceUI/input/variants/SelectInput/types.ts +34 -0
  134. package/src/SliceUI/input/variants/TextInput.tsx +68 -0
  135. package/src/SliceUI/layout/Box.tsx +38 -0
  136. package/src/SliceUI/layout/Center.tsx +38 -0
  137. package/src/SliceUI/layout/Divider.tsx +37 -0
  138. package/src/SliceUI/layout/Grid.tsx +75 -0
  139. package/src/SliceUI/layout/PageContainer.tsx +60 -0
  140. package/src/SliceUI/layout/ScrollContainer.tsx +72 -0
  141. package/src/SliceUI/layout/Spacer.tsx +54 -0
  142. package/src/SliceUI/layout/Stack.tsx +97 -0
  143. package/src/SliceUI/layout/StickyHeader.tsx +71 -0
  144. package/src/SliceUI/radio/RadioButton.tsx +130 -0
  145. package/src/SliceUI/radio/Token.ts +197 -0
  146. package/src/SliceUI/radio/Type.ts +35 -0
  147. package/src/SliceUI/react-native.config.js +3 -0
  148. package/src/SliceUI/responsive/Type.ts +7 -0
  149. package/src/SliceUI/responsive/helper.ts +53 -0
  150. package/src/SliceUI/switch/Switch.tsx +119 -0
  151. package/src/SliceUI/switch/Token.ts +205 -0
  152. package/src/SliceUI/switch/Type.ts +26 -0
  153. package/src/SliceUI/tab/TabItem.tsx +204 -0
  154. package/src/SliceUI/tab/Tabs.tsx +110 -0
  155. package/src/SliceUI/tab/Token.ts +282 -0
  156. package/src/SliceUI/tab/Type.ts +66 -0
  157. package/src/SliceUI/tab/helper.ts +53 -0
  158. package/src/SliceUI/table/Table.tsx +388 -0
  159. package/src/SliceUI/table/TableCell.tsx +158 -0
  160. package/src/SliceUI/table/TableFooter.tsx +353 -0
  161. package/src/SliceUI/table/TableHeader.tsx +247 -0
  162. package/src/SliceUI/table/TableRow.tsx +218 -0
  163. package/src/SliceUI/table/Token.ts +252 -0
  164. package/src/SliceUI/table/Type.ts +213 -0
  165. package/src/SliceUI/table/helper.ts +376 -0
  166. package/src/SliceUI/table/index.ts +53 -0
  167. package/src/SliceUI/theme/dummyColors.tsx +7 -0
  168. package/src/SliceUI/theme/theme.ts +107 -0
  169. package/src/SliceUI/typography/BaseTypographyToken.ts +62 -0
  170. package/src/SliceUI/typography/FoundationToken.ts +48 -0
  171. package/src/SliceUI/typography/Token.ts +228 -0
  172. package/src/SliceUI/typography/Type.ts +20 -0
  173. package/src/SliceUI/typography/Typography.tsx +99 -0
  174. package/src/SliceUI/values/BorderRadius.ts +17 -0
  175. package/src/SliceUI/values/BorderWidth.ts +7 -0
  176. package/src/SliceUI/values/Dimension.ts +35 -0
  177. package/src/SliceUI/values/IconSizes.ts +13 -0
  178. package/src/SliceUI/values/Spacing.ts +22 -0
  179. package/src/declarations.d.ts +8 -0
  180. package/src/index.tsx +119 -0
  181. package/src/stories/Colors.mdx +1418 -0
  182. package/src/stories/Dimensions.mdx +60 -0
  183. package/src/stories/GetStarted.mdx +90 -0
  184. package/src/stories/Introduction.mdx +136 -0
  185. package/src/stories/Shape.mdx +126 -0
  186. package/src/stories/Spacing.mdx +104 -0
  187. package/src/stories/Typography.mdx +454 -0
  188. package/src/stories/Utils.mdx +277 -0
  189. package/src/stories/story-components/AddIcon.js +13 -0
  190. package/src/stories/story-components/RectangleWithBox.jsx +51 -0
  191. package/src/stories/story-components/RoundedRectangle.jsx +18 -0
  192. package/src/stories/story-components/RoundedWithWhiteInside.jsx +33 -0
  193. package/src/stories/story-components/WhiteRoundedRectangle.jsx +107 -0
  194. package/src/stories/story-components/svgPaths.js +126 -0
@@ -0,0 +1,291 @@
1
+ import React, {useCallback, useMemo, useRef} from 'react';
2
+ import {spacing} from '../../../values/Spacing';
3
+ import {getCountries, getCountryCallingCode} from 'react-phone-number-input';
4
+ import type {CountryCode} from 'libphonenumber-js';
5
+ import type {PhoneInputProps} from './types';
6
+ import InputPortal from '../../components/InputPortal';
7
+
8
+ const webStyles = {
9
+ flagModeContainer: {
10
+ display: 'flex',
11
+ alignItems: 'center',
12
+ flex: 1,
13
+ } as const,
14
+ selectorButton: {
15
+ background: 'transparent',
16
+ border: 'none',
17
+ display: 'flex',
18
+ alignItems: 'center',
19
+ } as const,
20
+ codeSelectorButton: {
21
+ background: 'transparent',
22
+ border: 'none',
23
+ userSelect: 'none',
24
+ } as const,
25
+ flagImage: {
26
+ objectFit: 'cover',
27
+ } as const,
28
+ separator: {
29
+ width: 1,
30
+ margin: '0 4px',
31
+ } as const,
32
+ input: {
33
+ flex: 1,
34
+ border: 'none',
35
+ outline: 'none',
36
+ backgroundColor: 'transparent',
37
+ fontFamily: 'Lato',
38
+ } as const,
39
+ container: {
40
+ position: 'relative',
41
+ width: '100%',
42
+ } as const,
43
+ inputRow: {
44
+ display: 'flex',
45
+ alignItems: 'center',
46
+ width: '100%',
47
+ } as const,
48
+ dropdownOption: {
49
+ padding: '8px 12px',
50
+ cursor: 'pointer',
51
+ userSelect: 'none',
52
+ display: 'flex',
53
+ alignItems: 'center',
54
+ gap: 8,
55
+ } as const,
56
+ };
57
+
58
+ const WebPhoneInput = ({
59
+ mode,
60
+ country,
61
+ number,
62
+ dropdownOpen,
63
+ disabled,
64
+ labelText,
65
+ placeholder,
66
+ onToggleDropdown,
67
+ onCloseDropdown,
68
+ onSelectCountry,
69
+ onNumberChange,
70
+ onFocus,
71
+ onBlur,
72
+ colorTheme,
73
+ size = 'medium',
74
+ portalAnchorRef,
75
+ }: PhoneInputProps) => {
76
+ const containerRef = useRef<HTMLDivElement>(null);
77
+ const resolvedAnchorRef = portalAnchorRef ?? containerRef;
78
+ const handlePortalClose = onCloseDropdown ?? onToggleDropdown;
79
+
80
+ const fontSize =
81
+ {
82
+ small: '14px',
83
+ medium: '16px',
84
+ large: '18px',
85
+ xlarge: '20px',
86
+ xxlarge: '22px',
87
+ }[size] || '16px';
88
+
89
+ const minHeight =
90
+ {
91
+ small: '20px',
92
+ medium: '24px',
93
+ large: '32px',
94
+ xlarge: '36px',
95
+ xxlarge: '40px',
96
+ }[size] || '24px';
97
+
98
+ const padding =
99
+ {
100
+ small: '0 6px',
101
+ medium: '0 8px',
102
+ large: '0 12px',
103
+ xlarge: '0 14px',
104
+ xxlarge: '0 16px',
105
+ }[size] || '0 8px';
106
+
107
+ const flagSize = {
108
+ small: {width: 16, height: 12},
109
+ medium: {width: 20, height: 15},
110
+ large: {width: 24, height: 18},
111
+ xlarge: {width: 28, height: 21},
112
+ xxlarge: {width: 32, height: 24},
113
+ }[size] || {width: 20, height: 15};
114
+
115
+ const flagImageStyle = useMemo(
116
+ () => ({
117
+ ...webStyles.flagImage,
118
+ width: flagSize.width,
119
+ height: flagSize.height,
120
+ }),
121
+ [flagSize.height, flagSize.width],
122
+ );
123
+
124
+ const flagSelectorButtonStyle = useMemo(
125
+ () => ({
126
+ ...webStyles.selectorButton,
127
+ cursor: disabled ? 'not-allowed' : 'pointer',
128
+ padding,
129
+ }),
130
+ [disabled, padding],
131
+ );
132
+
133
+ const codeSelectorButtonStyle = useMemo(
134
+ () => ({
135
+ ...webStyles.codeSelectorButton,
136
+ cursor: disabled ? 'not-allowed' : 'pointer',
137
+ fontSize,
138
+ padding,
139
+ }),
140
+ [disabled, fontSize, padding],
141
+ );
142
+
143
+ const separatorStyle = useMemo(
144
+ () => ({
145
+ ...webStyles.separator,
146
+ height: minHeight,
147
+ backgroundColor: colorTheme.colors.colorBorderSubtle,
148
+ }),
149
+ [minHeight, colorTheme],
150
+ );
151
+
152
+ const inputStyle = useMemo(
153
+ () => ({
154
+ ...webStyles.input,
155
+ fontSize,
156
+ paddingLeft: spacing.space200,
157
+ paddingRight: spacing.space200,
158
+ color: colorTheme.colors.colorForegroundPrimary,
159
+ minHeight,
160
+ }),
161
+ [fontSize, minHeight, colorTheme],
162
+ );
163
+
164
+ const getCountryOptionStyle = useCallback(
165
+ (countryCode: string) => ({
166
+ ...webStyles.dropdownOption,
167
+ backgroundColor:
168
+ countryCode === country
169
+ ? colorTheme.colors.colorBorderAccent
170
+ : 'transparent',
171
+ color: colorTheme.colors.colorForegroundPrimary,
172
+ }),
173
+ [country, colorTheme],
174
+ );
175
+
176
+ const dropdownOptionLabelStyle = useMemo(() => ({fontSize}), [fontSize]);
177
+
178
+ const countries = useMemo(() => getCountries() as CountryCode[], []);
179
+
180
+ const countryOptions = useMemo(
181
+ () =>
182
+ countries.map(countryCode => ({
183
+ countryCode,
184
+ callingCode: getCountryCallingCode(countryCode),
185
+ })),
186
+ [countries],
187
+ );
188
+
189
+ const renderCountryOptions = useMemo(
190
+ () =>
191
+ countryOptions.map(({countryCode, callingCode}) => (
192
+ <div
193
+ key={countryCode}
194
+ onClick={() => onSelectCountry(countryCode)}
195
+ style={getCountryOptionStyle(countryCode)}>
196
+ {mode === 'flag' && (
197
+ <img
198
+ src={`https://flagcdn.com/w40/${countryCode.toLowerCase()}.png`}
199
+ alt={countryCode}
200
+ style={flagImageStyle}
201
+ />
202
+ )}
203
+
204
+ <span style={dropdownOptionLabelStyle}>
205
+ +{callingCode} {countryCode}
206
+ </span>
207
+ </div>
208
+ )),
209
+ [
210
+ countryOptions,
211
+ dropdownOptionLabelStyle,
212
+ flagImageStyle,
213
+ getCountryOptionStyle,
214
+ mode,
215
+ onSelectCountry,
216
+ ],
217
+ );
218
+
219
+ return (
220
+ <div ref={containerRef} style={webStyles.container}>
221
+ <div style={webStyles.inputRow}>
222
+ {mode === 'flag' ? (
223
+ <div style={webStyles.flagModeContainer}>
224
+ <button
225
+ type="button"
226
+ onClick={onToggleDropdown}
227
+ aria-label="Select country"
228
+ disabled={disabled}
229
+ style={flagSelectorButtonStyle}>
230
+ <img
231
+ src={`https://flagcdn.com/w40/${country.toLowerCase()}.png`}
232
+ alt={country}
233
+ style={flagImageStyle}
234
+ />
235
+ </button>
236
+
237
+ <div style={separatorStyle} />
238
+
239
+ <input
240
+ type="tel"
241
+ value={number}
242
+ onChange={e => onNumberChange(e.target.value)}
243
+ onFocus={onFocus}
244
+ onBlur={onBlur}
245
+ placeholder={!labelText ? placeholder : undefined}
246
+ disabled={disabled}
247
+ style={inputStyle}
248
+ aria-label={labelText || placeholder}
249
+ />
250
+ </div>
251
+ ) : (
252
+ <>
253
+ <button
254
+ type="button"
255
+ onClick={onToggleDropdown}
256
+ disabled={disabled}
257
+ aria-label="Select country code"
258
+ style={codeSelectorButtonStyle}>
259
+ +{getCountryCallingCode(country)}
260
+ </button>
261
+
262
+ <div style={separatorStyle} />
263
+
264
+ <input
265
+ type="tel"
266
+ value={number}
267
+ onChange={e => onNumberChange(e.target.value)}
268
+ onFocus={onFocus}
269
+ onBlur={onBlur}
270
+ placeholder={!labelText ? placeholder : undefined}
271
+ disabled={disabled}
272
+ style={inputStyle}
273
+ aria-label={labelText || placeholder}
274
+ />
275
+ </>
276
+ )}
277
+ </div>
278
+
279
+ <InputPortal
280
+ open={dropdownOpen}
281
+ anchorRef={resolvedAnchorRef}
282
+ borderColor={colorTheme.colors.colorBorderSubtle}
283
+ backgroundColor={colorTheme.colors.colorBackgroundPrimary}
284
+ onRequestClose={handlePortalClose}>
285
+ {renderCountryOptions}
286
+ </InputPortal>
287
+ </div>
288
+ );
289
+ };
290
+
291
+ export default React.memo(WebPhoneInput);
@@ -0,0 +1,22 @@
1
+ // types.ts
2
+ import type React from 'react';
3
+ import type {CountryCode} from 'libphonenumber-js';
4
+
5
+ export interface PhoneInputProps {
6
+ mode: 'flag' | 'code';
7
+ country: CountryCode;
8
+ number: string;
9
+ dropdownOpen: boolean;
10
+ disabled?: boolean;
11
+ labelText?: string;
12
+ placeholder?: string;
13
+ onToggleDropdown: () => void;
14
+ onCloseDropdown?: () => void;
15
+ onSelectCountry: (country: CountryCode) => void;
16
+ onNumberChange: (value: string) => void;
17
+ onFocus?: (e: any) => void;
18
+ onBlur?: (e: any) => void;
19
+ colorTheme: any;
20
+ size?: 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge';
21
+ portalAnchorRef?: React.RefObject<HTMLElement>;
22
+ }
@@ -0,0 +1,407 @@
1
+ import React, { memo, useMemo, useCallback, useEffect } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ TextInput,
7
+ TouchableOpacity,
8
+ FlatList,
9
+ ScrollView,
10
+ LayoutAnimation,
11
+ UIManager,
12
+ } from 'react-native';
13
+ import { spacing } from '../../../values/Spacing';
14
+ import SelectChips from '../../components/SelectChip';
15
+ import SelectListItem from '../../components/SelectListItem';
16
+ import NativeBottomSheet from '../../components/NativeBottomSheet';
17
+ import Typography from '../../../typography/Typography';
18
+ import { useSliceTheme } from '../../../contextProvider/context';
19
+ import type { SelectInputProps } from './types';
20
+ import type { SelectOption } from '../../Type';
21
+ import { isWeb } from '../../utils/inputUtils';
22
+
23
+ const MAX_VISIBLE_CHIPS = 3;
24
+ const ITEM_HEIGHT = 52;
25
+
26
+ const SelectInput = memo(({
27
+ value,
28
+ displayValue,
29
+ searchable = false,
30
+ normalizedMultiple,
31
+ normalizedListType = 'default',
32
+ selectDropdownOpen,
33
+ filteredOptions,
34
+ options,
35
+ searchValue,
36
+ setSearchValue,
37
+ onOpen,
38
+ onClose,
39
+ onKeyDown,
40
+ onOptionSelect,
41
+ onCheckboxToggle,
42
+ colorTheme,
43
+ placeholder,
44
+ noItemText = 'No items',
45
+ maxAllowed,
46
+ maxVisibleItems,
47
+ selectedIcon,
48
+ selectedValues,
49
+ disabled = false,
50
+ registerOptionRef: _registerOptionRef,
51
+ inputRef,
52
+ size = 'medium',
53
+ expandChips = false,
54
+ }: SelectInputProps) => {
55
+ const { deviceBreakpoint } = useSliceTheme();
56
+ const isWebMobile = isWeb && (deviceBreakpoint === 'xs' || deviceBreakpoint === 'sm');
57
+ const useBottomSheetMode = !isWeb || isWebMobile;
58
+
59
+ useEffect(() => {
60
+ if (!isWeb && UIManager.setLayoutAnimationEnabledExperimental) {
61
+ UIManager.setLayoutAnimationEnabledExperimental(true);
62
+ }
63
+ }, []);
64
+
65
+ const hasMultipleValues = useMemo(() => {
66
+ return !!normalizedMultiple && Array.isArray(value) && value.length > 0;
67
+ }, [normalizedMultiple, value]);
68
+
69
+ const labels = useMemo(() => {
70
+ return selectedValues
71
+ .map(v => options.find(o => o.value === v)?.label)
72
+ .filter(Boolean) as string[];
73
+ }, [selectedValues, options]);
74
+
75
+ const visibleLabels = expandChips ? labels : labels.slice(0, MAX_VISIBLE_CHIPS);
76
+ const visibleValues = expandChips ? selectedValues : selectedValues.slice(0, MAX_VISIBLE_CHIPS);
77
+ const overflowCount = expandChips ? 0 : Math.max(labels.length - MAX_VISIBLE_CHIPS, 0);
78
+ const nativeListMaxHeight = useMemo(() => {
79
+ return maxVisibleItems && maxVisibleItems > 0
80
+ ? maxVisibleItems * ITEM_HEIGHT
81
+ : 280;
82
+ }, [maxVisibleItems]);
83
+
84
+ const hasReachedMaxAllowed = useMemo(() => {
85
+ return !!normalizedMultiple
86
+ && maxAllowed !== undefined
87
+ && selectedValues.length >= maxAllowed;
88
+ }, [normalizedMultiple, maxAllowed, selectedValues.length]);
89
+
90
+ const inputTextColor = disabled
91
+ ? colorTheme.colors.colorForegroundTertiary
92
+ : colorTheme.colors.colorForegroundPrimary;
93
+
94
+ const searchableWithChipsRowStyle = useMemo(
95
+ () => [
96
+ styles.searchableWithChipsRow,
97
+ expandChips ? styles.searchableWithChipsRowWrap : styles.searchableWithChipsRowNoWrap,
98
+ ],
99
+ [expandChips],
100
+ );
101
+
102
+ const inputTextColorStyle = useMemo(
103
+ () => ({
104
+ color: inputTextColor,
105
+ }),
106
+ [inputTextColor],
107
+ );
108
+
109
+ const bottomSheetSearchInputThemeStyle = useMemo(
110
+ () => ({
111
+ color: inputTextColor,
112
+ borderColor: colorTheme.colors.colorBorderSubtle,
113
+ backgroundColor: colorTheme.colors.colorBackgroundPrimary,
114
+ }),
115
+ [inputTextColor, colorTheme],
116
+ );
117
+
118
+ const emptyTextStyle = useMemo(
119
+ () => ({
120
+ color: colorTheme.colors.colorForegroundSecondary,
121
+ }),
122
+ [colorTheme],
123
+ );
124
+
125
+ const nativeListMaxHeightStyle = useMemo(
126
+ () => ({
127
+ maxHeight: nativeListMaxHeight,
128
+ }),
129
+ [nativeListMaxHeight],
130
+ );
131
+
132
+ const webSearchInputFocusResetStyle = useMemo(
133
+ () => ({
134
+ ['outlineStyle' as any]: 'none',
135
+ ['outlineWidth' as any]: 0,
136
+ ['outlineColor' as any]: 'transparent',
137
+ ['boxShadow' as any]: 'none',
138
+ }),
139
+ [],
140
+ );
141
+
142
+ const handleSearchChange = useCallback((inputValue: string) => {
143
+ if (disabled) {return;}
144
+
145
+ setSearchValue(inputValue);
146
+ if (inputValue && !selectDropdownOpen) {onOpen();}
147
+ }, [disabled, selectDropdownOpen, onOpen, setSearchValue]);
148
+
149
+ useEffect(() => {
150
+ if (!isWeb && normalizedMultiple && expandChips) {
151
+ LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
152
+ }
153
+ }, [expandChips, normalizedMultiple, visibleLabels.length, overflowCount]);
154
+
155
+ const renderChips = () => (
156
+ <>
157
+ <SelectChips
158
+ labels={visibleLabels}
159
+ values={visibleValues}
160
+ colorTheme={colorTheme}
161
+ disabled={disabled}
162
+ size={size}
163
+ onRemove={onOptionSelect}
164
+ wrap={expandChips}
165
+ />
166
+ {overflowCount > 0 && (
167
+ <SelectChips
168
+ labels={[`+${overflowCount}`]}
169
+ colorTheme={colorTheme}
170
+ disabled
171
+ size={size}
172
+ wrap={expandChips}
173
+ />
174
+ )}
175
+ </>
176
+ );
177
+
178
+ const renderNativeOption = ({ item, index }: { item: SelectOption; index: number }) => {
179
+ const isSelected = normalizedMultiple
180
+ ? selectedValues.includes(item.value)
181
+ : value === item.value;
182
+ const optionDisabled = disabled || (hasReachedMaxAllowed && !isSelected);
183
+
184
+ return (
185
+ <SelectListItem
186
+ option={item}
187
+ index={index}
188
+ isSelected={isSelected}
189
+ normalizedListType={normalizedListType}
190
+ selectedIcon={selectedIcon}
191
+ onOptionSelect={(selectedValue: string) => {
192
+ onOptionSelect(selectedValue);
193
+ if (!normalizedMultiple) {
194
+ onClose();
195
+ }
196
+ }}
197
+ onCheckboxToggle={onCheckboxToggle}
198
+ colorTheme={colorTheme}
199
+ disabled={optionDisabled}
200
+ />
201
+ );
202
+ };
203
+
204
+ return (
205
+ <>
206
+ {isWeb && searchable && !isWebMobile ? (
207
+ hasMultipleValues ? (
208
+ <View style={styles.searchableWithChipsContainer}>
209
+ <View style={searchableWithChipsRowStyle}>
210
+ {renderChips()}
211
+ <TextInput
212
+ ref={inputRef as any}
213
+ value={searchValue}
214
+ onChangeText={handleSearchChange}
215
+ onFocus={disabled ? undefined : onOpen}
216
+ editable={!disabled}
217
+ style={[
218
+ styles.searchInputWithChips,
219
+ inputTextColorStyle,
220
+ webSearchInputFocusResetStyle,
221
+ ]}
222
+ {...(isWeb ? { onKeyDown: onKeyDown as any } : {})}
223
+ />
224
+ </View>
225
+ </View>
226
+ ) : (
227
+ <TextInput
228
+ ref={inputRef as any}
229
+ value={selectDropdownOpen ? searchValue : (searchValue || displayValue)}
230
+ onChangeText={handleSearchChange}
231
+ onFocus={disabled ? undefined : onOpen}
232
+ editable={!disabled}
233
+ placeholder={!displayValue ? placeholder : ''}
234
+ placeholderTextColor={colorTheme.colors.colorForegroundTertiary}
235
+ style={[
236
+ styles.searchInput,
237
+ inputTextColorStyle,
238
+ webSearchInputFocusResetStyle,
239
+ ]}
240
+ {...(isWeb ? { onKeyDown: onKeyDown as any } : {})}
241
+ />
242
+ )
243
+ ) : (
244
+ <TouchableOpacity
245
+ onPress={disabled ? undefined : onOpen}
246
+ activeOpacity={disabled ? 1 : 0.7}
247
+ style={[
248
+ styles.triggerContainer,
249
+ hasMultipleValues && styles.triggerContainerMultiple,
250
+ hasMultipleValues && expandChips && styles.triggerContainerExpanded,
251
+ ]}
252
+ {...(isWeb
253
+ ? {
254
+ className: 'select-button',
255
+ onClick: disabled ? undefined : onOpen,
256
+ }
257
+ : {})}
258
+ >
259
+ {hasMultipleValues ? (
260
+ expandChips ? (
261
+ <ScrollView
262
+ horizontal
263
+ showsHorizontalScrollIndicator={false}
264
+ style={styles.chipsScroll}
265
+ contentContainerStyle={styles.chipsContainerScroll}
266
+ >
267
+ <View style={styles.chipsContainerRow}>
268
+ {renderChips()}
269
+ </View>
270
+ </ScrollView>
271
+ ) : (
272
+ <View style={styles.chipsContainer}>
273
+ {renderChips()}
274
+ </View>
275
+ )
276
+ ) : (
277
+ <Typography
278
+ variant="body2Regular"
279
+ style={inputTextColorStyle}
280
+ >
281
+ {displayValue}
282
+ </Typography>
283
+ )}
284
+ </TouchableOpacity>
285
+ )}
286
+
287
+ {useBottomSheetMode ? (
288
+ <NativeBottomSheet
289
+ open={selectDropdownOpen}
290
+ onClose={onClose}
291
+ title={placeholder}
292
+ fitContent
293
+ avoidKeyboardForHeader={searchable}
294
+ headerContent={searchable ? (
295
+ <TextInput
296
+ ref={inputRef as any}
297
+ value={searchValue}
298
+ onChangeText={setSearchValue}
299
+ placeholder="Search..."
300
+ placeholderTextColor={colorTheme.colors.colorForegroundTertiary}
301
+ editable={!disabled}
302
+ autoFocus
303
+ style={[
304
+ styles.bottomSheetSearchInput,
305
+ bottomSheetSearchInputThemeStyle,
306
+ ]}
307
+ />
308
+ ) : undefined}
309
+ colorTheme={colorTheme}
310
+ >
311
+ {filteredOptions.length === 0 ? (
312
+ <View style={styles.emptyContainer}>
313
+ <Text style={emptyTextStyle}>
314
+ {noItemText}
315
+ </Text>
316
+ </View>
317
+ ) : (
318
+ <View style={[styles.nativeListContainer, nativeListMaxHeightStyle]}>
319
+ <FlatList
320
+ data={filteredOptions}
321
+ keyExtractor={(item) => item.value}
322
+ renderItem={renderNativeOption}
323
+ keyboardShouldPersistTaps="handled"
324
+ />
325
+ </View>
326
+ )}
327
+ </NativeBottomSheet>
328
+ ) : null}
329
+ </>
330
+ );
331
+ });
332
+
333
+ SelectInput.displayName = 'SelectInput';
334
+
335
+ const styles = StyleSheet.create({
336
+ searchableWithChipsContainer: {
337
+ flex: 1,
338
+ paddingHorizontal: spacing.space200,
339
+ },
340
+ searchableWithChipsRow: {
341
+ flex: 1,
342
+ flexDirection: 'row',
343
+ alignItems: 'center',
344
+ },
345
+ searchableWithChipsRowWrap: {
346
+ flexWrap: 'wrap',
347
+ },
348
+ searchableWithChipsRowNoWrap: {
349
+ flexWrap: 'nowrap',
350
+ },
351
+ searchInputWithChips: {
352
+ flex: 1,
353
+ minWidth: 40,
354
+ marginLeft: spacing.space100,
355
+ backgroundColor: 'transparent',
356
+ },
357
+ searchInput: {
358
+ flex: 1,
359
+ paddingHorizontal: spacing.space200,
360
+ backgroundColor: 'transparent',
361
+ },
362
+ triggerContainer: {
363
+ flex: 1,
364
+ justifyContent: 'center',
365
+ paddingHorizontal: spacing.space200,
366
+ },
367
+ triggerContainerMultiple: {
368
+ paddingVertical: spacing.space50,
369
+ alignItems: 'stretch',
370
+ },
371
+ triggerContainerExpanded: {
372
+ paddingVertical: 0,
373
+ },
374
+ chipsContainer: {
375
+ flexDirection: 'row',
376
+ alignItems: 'center',
377
+ width: '100%',
378
+ flexShrink: 1,
379
+ },
380
+ chipsScroll: {
381
+ width: '100%',
382
+ },
383
+ chipsContainerScroll: {
384
+ alignItems: 'center',
385
+ paddingVertical: spacing.space50,
386
+ paddingRight: spacing.space100,
387
+ },
388
+ chipsContainerRow: {
389
+ flexDirection: 'row',
390
+ alignItems: 'center',
391
+ },
392
+ emptyContainer: {
393
+ padding: 32,
394
+ alignItems: 'center',
395
+ },
396
+ nativeListContainer: {
397
+ width: '100%',
398
+ },
399
+ bottomSheetSearchInput: {
400
+ height: 40,
401
+ borderWidth: 1,
402
+ borderRadius: 10,
403
+ paddingHorizontal: 12,
404
+ },
405
+ });
406
+
407
+ export default SelectInput;