aport-tools 4.2.0 → 4.2.2

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.
@@ -0,0 +1,84 @@
1
+ import React from 'react';
2
+ interface Option {
3
+ id: number | string;
4
+ label: string;
5
+ value: any;
6
+ }
7
+ interface InputListProps {
8
+ name: string;
9
+ /**
10
+ * Placeholder text displayed in the input box when no selection is made.
11
+ * @type {string}
12
+ * @default "Choose value/s"
13
+ */
14
+ placeholder?: string;
15
+ /**
16
+ * Custom styles for the component.
17
+ * @type {object}
18
+ */
19
+ style?: object;
20
+ /**
21
+ * Array of options to display in the dropdown. Each option should have an id, label, and value.
22
+ * @type {Option[]}
23
+ */
24
+ options: Option[];
25
+ /**
26
+ * Enables multi-selection mode when true. If enabled, users can select multiple values.
27
+ * @type {boolean}
28
+ * @default false
29
+ */
30
+ multi?: boolean;
31
+ /**
32
+ * Disables the dropdown input when true.
33
+ * @type {boolean}
34
+ * @default false
35
+ */
36
+ disabled?: boolean;
37
+ /**
38
+ * Key used to sort options in the dropdown (e.g., by 'id', 'label').
39
+ * @type {keyof Option}
40
+ */
41
+ sortBy?: keyof Option;
42
+ /**
43
+ * If true, displays a separator line between each option.
44
+ * @type {boolean}
45
+ * @default false
46
+ */
47
+ separator?: boolean;
48
+ /**
49
+ * Closes the dropdown if the user scrolls the list.
50
+ * @type {boolean}
51
+ * @default false
52
+ */
53
+ closeOnScroll?: boolean;
54
+ /**
55
+ * Closes the dropdown after selecting an item if true (only relevant when multi is false).
56
+ * @type {boolean}
57
+ * @default true
58
+ */
59
+ closeOnSelect?: boolean;
60
+ /**
61
+ * Limits the maximum number of items that can be selected when multi is true.
62
+ * Once the limit is reached, the dropdown closes, and no further selections can be made until
63
+ * an item is deselected.
64
+ * @type {number}
65
+ */
66
+ maxSelection?: number;
67
+ }
68
+ /**
69
+ * InputList component - A custom dropdown list component for React Native with multi-selection support,
70
+ * customizable styling, sorting, and configurable close behavior on selection or scrolling.
71
+ *
72
+ * @param {string} placeholder - Placeholder text for the input.
73
+ * @param {object} style - Custom styles for the component.
74
+ * @param {Option[]} options - Array of options to display in the dropdown.
75
+ * @param {boolean} multi - Enables multi-selection mode.
76
+ * @param {boolean} disabled - Disables the dropdown input.
77
+ * @param {keyof Option} sortBy - Key to sort options by (e.g., 'id').
78
+ * @param {boolean} separator - If true, adds a separator line between options.
79
+ * @param {boolean} closeOnScroll - Closes the dropdown if the user scrolls the list.
80
+ * @param {boolean} closeOnSelect - Closes the dropdown on selection in single-select mode.
81
+ * @param {number} maxSelection - Maximum number of items selectable in multi-select mode.
82
+ */
83
+ export declare const InputList: React.FC<InputListProps>;
84
+ export {};
@@ -3,3 +3,4 @@ export { Form, useFormContext } from './FormContext';
3
3
  export { default as TextArea } from './TextArea';
4
4
  export { default as Label } from './Label';
5
5
  export { default as ErrorList } from './ErrorList';
6
+ export { InputList } from './InputList';
package/dist/index.esm.js CHANGED
@@ -1,6 +1,6 @@
1
- /*! aport-tools v4.2.0 | ISC */
2
- import React, { useContext, useState, createContext, useMemo } from 'react';
3
- import { StyleSheet, Text as Text$1, View, TextInput, TouchableOpacity, ActivityIndicator, Platform } from 'react-native';
1
+ /*! aport-tools v4.2.2 | ISC */
2
+ import React, { useContext, useState, createContext, useCallback, useMemo } from 'react';
3
+ import { StyleSheet, Text as Text$1, View, TextInput, TouchableOpacity, Modal, Pressable, FlatList, Keyboard, ActivityIndicator, Platform } from 'react-native';
4
4
  import { ThemeContext } from 'aport-themes';
5
5
 
6
6
  /******************************************************************************
@@ -81,6 +81,16 @@ function __generator(thisArg, body) {
81
81
  }
82
82
  }
83
83
 
84
+ function __spreadArray(to, from, pack) {
85
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
86
+ if (ar || !(i in from)) {
87
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
88
+ ar[i] = from[i];
89
+ }
90
+ }
91
+ return to.concat(ar || Array.prototype.slice.call(from));
92
+ }
93
+
84
94
  typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
85
95
  var e = new Error(message);
86
96
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
@@ -220,23 +230,23 @@ var ErrorList = function ErrorList(_a) {
220
230
  var theme = useContext(ThemeContext).theme;
221
231
  var colors = theme.colors;
222
232
  return /*#__PURE__*/React.createElement(View, {
223
- style: styles$5.container
233
+ style: styles$6.container
224
234
  }, errors.map(function (error, index) {
225
235
  return /*#__PURE__*/React.createElement(View, {
226
236
  key: index,
227
- style: styles$5.errorItem
237
+ style: styles$6.errorItem
228
238
  }, /*#__PURE__*/React.createElement(Text, {
229
- style: [styles$5.bullet, {
239
+ style: [styles$6.bullet, {
230
240
  color: colors.error.hex
231
241
  }]
232
242
  }, "\u2022"), /*#__PURE__*/React.createElement(Text, {
233
- style: [styles$5.errorText, {
243
+ style: [styles$6.errorText, {
234
244
  color: colors.error.hex
235
245
  }]
236
246
  }, error));
237
247
  }));
238
248
  };
239
- var styles$5 = StyleSheet.create({
249
+ var styles$6 = StyleSheet.create({
240
250
  container: {
241
251
  marginTop: 4
242
252
  },
@@ -314,13 +324,13 @@ var Input = function Input(_a) {
314
324
  setFormValue(name, formattedText);
315
325
  };
316
326
  return /*#__PURE__*/React.createElement(View, {
317
- style: styles$4.container
327
+ style: styles$5.container
318
328
  }, /*#__PURE__*/React.createElement(Text, {
319
- style: [styles$4.label, {
329
+ style: [styles$5.label, {
320
330
  color: colors.text.hex
321
331
  }]
322
332
  }, label), /*#__PURE__*/React.createElement(TextInput, __assign({
323
- style: [styles$4.input, {
333
+ style: [styles$5.input, {
324
334
  backgroundColor: colors.body.hex,
325
335
  borderColor: formErrors[name] ? colors.error.hex : "#CCC",
326
336
  color: colors.text.hex
@@ -333,7 +343,7 @@ var Input = function Input(_a) {
333
343
  errors: formErrors[name]
334
344
  })));
335
345
  };
336
- var styles$4 = StyleSheet.create({
346
+ var styles$5 = StyleSheet.create({
337
347
  container: {
338
348
  marginBottom: 16
339
349
  },
@@ -364,11 +374,11 @@ var TextArea = function TextArea(_a) {
364
374
  setFormValue(name, text);
365
375
  };
366
376
  return /*#__PURE__*/React.createElement(View, {
367
- style: styles$3.container
377
+ style: styles$4.container
368
378
  }, /*#__PURE__*/React.createElement(Text, {
369
- style: styles$3.label
379
+ style: styles$4.label
370
380
  }, label), /*#__PURE__*/React.createElement(TextInput, __assign({
371
- style: [styles$3.textArea, style],
381
+ style: [styles$4.textArea, style],
372
382
  value: formValues[name] || '',
373
383
  onChangeText: handleChange,
374
384
  placeholder: label,
@@ -381,7 +391,7 @@ var TextArea = function TextArea(_a) {
381
391
  errors: formErrors[name]
382
392
  })));
383
393
  };
384
- var styles$3 = StyleSheet.create({
394
+ var styles$4 = StyleSheet.create({
385
395
  container: {
386
396
  marginBottom: 16
387
397
  },
@@ -410,18 +420,214 @@ var Label = function Label(_a) {
410
420
  var theme = useContext(ThemeContext).theme;
411
421
  var colors = theme.colors;
412
422
  return /*#__PURE__*/React.createElement(Text, {
413
- style: [styles$2.label, style, {
423
+ style: [styles$3.label, style, {
414
424
  color: colors.text.hex
415
425
  }]
416
426
  }, text);
417
427
  };
418
- var styles$2 = StyleSheet.create({
428
+ var styles$3 = StyleSheet.create({
419
429
  label: {
420
430
  marginBottom: 4,
421
431
  fontWeight: '500'
422
432
  }
423
433
  });
424
434
 
435
+ /**
436
+ * InputList component - A custom dropdown list component for React Native with multi-selection support,
437
+ * customizable styling, sorting, and configurable close behavior on selection or scrolling.
438
+ *
439
+ * @param {string} placeholder - Placeholder text for the input.
440
+ * @param {object} style - Custom styles for the component.
441
+ * @param {Option[]} options - Array of options to display in the dropdown.
442
+ * @param {boolean} multi - Enables multi-selection mode.
443
+ * @param {boolean} disabled - Disables the dropdown input.
444
+ * @param {keyof Option} sortBy - Key to sort options by (e.g., 'id').
445
+ * @param {boolean} separator - If true, adds a separator line between options.
446
+ * @param {boolean} closeOnScroll - Closes the dropdown if the user scrolls the list.
447
+ * @param {boolean} closeOnSelect - Closes the dropdown on selection in single-select mode.
448
+ * @param {number} maxSelection - Maximum number of items selectable in multi-select mode.
449
+ */
450
+ var InputList = function InputList(_a) {
451
+ var name = _a.name,
452
+ _b = _a.placeholder,
453
+ placeholder = _b === void 0 ? "Choose value/s" : _b,
454
+ style = _a.style,
455
+ options = _a.options,
456
+ _c = _a.multi,
457
+ multi = _c === void 0 ? false : _c,
458
+ _d = _a.disabled,
459
+ disabled = _d === void 0 ? false : _d,
460
+ sortBy = _a.sortBy,
461
+ _e = _a.separator,
462
+ separator = _e === void 0 ? false : _e,
463
+ _f = _a.closeOnScroll,
464
+ closeOnScroll = _f === void 0 ? false : _f,
465
+ _g = _a.closeOnSelect,
466
+ closeOnSelect = _g === void 0 ? true : _g,
467
+ maxSelection = _a.maxSelection;
468
+ var _h = useFormContext(),
469
+ formValues = _h.formValues,
470
+ setFormValue = _h.setFormValue;
471
+ var _j = useState(false),
472
+ isDropdownVisible = _j[0],
473
+ setIsDropdownVisible = _j[1];
474
+ var selectedOptions = formValues[name] || (multi ? [] : null);
475
+ var sortedOptions = sortBy ? __spreadArray([], options, true).sort(function (a, b) {
476
+ return a[sortBy] > b[sortBy] ? 1 : -1;
477
+ }) : options;
478
+ var theme = useContext(ThemeContext).theme;
479
+ var colors = theme.colors;
480
+ /**
481
+ * Handles selection of an option. Adds or removes the option from selectedOptions based on
482
+ * multi-selection and maxSelection criteria.
483
+ * @param {Option} option - The selected option object.
484
+ */
485
+ var handleSelectOption = function handleSelectOption(option) {
486
+ if (multi) {
487
+ var alreadySelected = selectedOptions.some(function (opt) {
488
+ return opt.id === option.id;
489
+ });
490
+ // Add or remove item from selected options
491
+ var updatedSelections = alreadySelected ? selectedOptions.filter(function (opt) {
492
+ return opt.id !== option.id;
493
+ }) : __spreadArray(__spreadArray([], selectedOptions, true), [option], false);
494
+ // Close dropdown if max selection is reached after selecting
495
+ if (!alreadySelected && maxSelection && updatedSelections.length >= maxSelection) {
496
+ setIsDropdownVisible(false);
497
+ }
498
+ // Update form value
499
+ setFormValue(name, updatedSelections);
500
+ } else {
501
+ setFormValue(name, option);
502
+ if (closeOnSelect) setIsDropdownVisible(false);
503
+ }
504
+ };
505
+ /**
506
+ * Renders selected options as a comma-separated string or the placeholder if none selected.
507
+ * @returns {string} - The display text for selected options or placeholder.
508
+ */
509
+ var renderSelectedText = function renderSelectedText() {
510
+ if (multi) return selectedOptions.map(function (opt) {
511
+ return opt.label;
512
+ }).join(', ') || placeholder;
513
+ return (selectedOptions === null || selectedOptions === void 0 ? void 0 : selectedOptions.label) || placeholder;
514
+ };
515
+ /**
516
+ * Toggles dropdown visibility. Disables toggle if the component is disabled.
517
+ */
518
+ var toggleDropdown = function toggleDropdown() {
519
+ if (!disabled) {
520
+ setIsDropdownVisible(!isDropdownVisible);
521
+ if (!isDropdownVisible) Keyboard.dismiss();
522
+ }
523
+ };
524
+ /**
525
+ * Closes the dropdown when pressing outside.
526
+ */
527
+ var handleCloseDropdown = useCallback(function () {
528
+ if (isDropdownVisible) setIsDropdownVisible(false);
529
+ }, [isDropdownVisible]);
530
+ // Conditionally render item as disabled if max selection reached and item is unselected
531
+ var isItemDisabled = function isItemDisabled(option) {
532
+ return multi && maxSelection && selectedOptions.length >= maxSelection && !selectedOptions.some(function (opt) {
533
+ return opt.id === option.id;
534
+ });
535
+ };
536
+ return /*#__PURE__*/React.createElement(View, {
537
+ style: [styles$2.container, style]
538
+ }, /*#__PURE__*/React.createElement(TouchableOpacity, {
539
+ style: styles$2.inputContainer,
540
+ onPress: toggleDropdown,
541
+ disabled: disabled
542
+ }, /*#__PURE__*/React.createElement(Text$1, {
543
+ style: {
544
+ color: colors.text.hex
545
+ }
546
+ }, renderSelectedText())), /*#__PURE__*/React.createElement(Modal, {
547
+ visible: isDropdownVisible,
548
+ transparent: true,
549
+ animationType: "fade"
550
+ }, /*#__PURE__*/React.createElement(Pressable, {
551
+ style: styles$2.overlay,
552
+ onPress: handleCloseDropdown
553
+ }), /*#__PURE__*/React.createElement(View, {
554
+ style: [styles$2.dropdownContainer, {
555
+ backgroundColor: colors.body.hex
556
+ }]
557
+ }, /*#__PURE__*/React.createElement(FlatList, {
558
+ data: sortedOptions,
559
+ keyExtractor: function keyExtractor(item) {
560
+ return item.id.toString();
561
+ },
562
+ renderItem: function renderItem(_a) {
563
+ var item = _a.item;
564
+ var isSelected = selectedOptions.some(function (opt) {
565
+ return opt.id === item.id;
566
+ });
567
+ var isDisabled = isItemDisabled(item);
568
+ return /*#__PURE__*/React.createElement(TouchableOpacity, {
569
+ onPress: function onPress() {
570
+ return handleSelectOption(item);
571
+ },
572
+ style: [styles$2.optionItem, isSelected ? {
573
+ backgroundColor: colors.primary.hex
574
+ } : {}, isDisabled ? styles$2.disabledItem : {}],
575
+ disabled: !!isDisabled
576
+ }, /*#__PURE__*/React.createElement(Text$1, {
577
+ style: [{
578
+ color: colors.primary.hex
579
+ }, isDisabled ? styles$2.disabledText : {}]
580
+ }, item.label));
581
+ },
582
+ ItemSeparatorComponent: function ItemSeparatorComponent() {
583
+ return separator ? /*#__PURE__*/React.createElement(View, {
584
+ style: styles$2.separator
585
+ }) : null;
586
+ },
587
+ scrollEnabled: !closeOnScroll
588
+ }))));
589
+ };
590
+ var styles$2 = StyleSheet.create({
591
+ container: {
592
+ padding: 8
593
+ },
594
+ inputContainer: {
595
+ padding: 12,
596
+ borderWidth: 1,
597
+ borderColor: '#ccc',
598
+ borderRadius: 5
599
+ },
600
+ dropdownContainer: {
601
+ position: 'absolute',
602
+ top: '30%',
603
+ // Center the dropdown vertically
604
+ alignSelf: 'center',
605
+ width: '90%',
606
+ backgroundColor: '#fff',
607
+ borderRadius: 8,
608
+ elevation: 5,
609
+ paddingVertical: 10
610
+ },
611
+ optionItem: {
612
+ padding: 12
613
+ },
614
+ disabledItem: {
615
+ backgroundColor: '#f0f0f0'
616
+ },
617
+ disabledText: {
618
+ color: '#999'
619
+ },
620
+ separator: {
621
+ height: 1,
622
+ backgroundColor: '#ddd',
623
+ marginHorizontal: 8
624
+ },
625
+ overlay: {
626
+ flex: 1,
627
+ backgroundColor: 'rgba(0,0,0,0.3)'
628
+ }
629
+ });
630
+
425
631
  // src/components/Button.tsx
426
632
  /**
427
633
  * Determines the styles based on the button type and whether it is disabled.
@@ -600,5 +806,5 @@ var styles = StyleSheet.create({
600
806
  }
601
807
  });
602
808
 
603
- export { Button, Card, ErrorList, Form, Input, Label, Text, TextArea, useFormContext };
809
+ export { Button, Card, ErrorList, Form, Input, InputList, Label, Text, TextArea, useFormContext };
604
810
  //# sourceMappingURL=index.esm.js.map