@pagamio/frontend-commons-lib 0.8.253 → 0.8.255

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.
@@ -1,13 +1,12 @@
1
1
  import React from 'react';
2
- interface FeatureItem {
2
+ export interface FeatureItem {
3
3
  icon?: React.ReactNode;
4
4
  title: string;
5
5
  description: string;
6
6
  }
7
- interface FeatureCarouselProps {
7
+ export interface FeatureCarouselProps {
8
8
  features: FeatureItem[];
9
9
  className?: string;
10
10
  interval?: number;
11
11
  }
12
12
  export declare function FeatureCarousel({ features, className, interval }: FeatureCarouselProps): import("react/jsx-runtime").JSX.Element | null;
13
- export {};
@@ -6,5 +6,6 @@ export { default as PagamioCustomerRegistrationPage, customerRegistrationPageDef
6
6
  export { default as PagamioForgotPasswordPage, forgotPasswordDefaultText, type PagamioForgotPasswordPageProps, } from './ForgotPasswordPage';
7
7
  export { default as PagamioResetPasswordPage, resetPasswordDefaultText, type PagamioResetPasswordPageProps, } from './ResetPasswordPage';
8
8
  export { AuthPageLayout as PagamioAuthPageLayout } from './AuthPageLayout';
9
+ export { FeatureCarousel, type FeatureCarouselProps, type FeatureItem } from './FeatureCarousel';
9
10
  export { default as OtpVerificationPage, OtpInput, otpVerificationDefaultText, type OtpInputProps, type OtpVerificationConfig, type OtpVerificationHandlers, type OtpVerificationPageProps, type OtpVerificationText, } from './OtpVerification';
10
11
  export { passwordValidation } from './AuthFormUtils';
@@ -5,5 +5,6 @@ export { default as PagamioCustomerRegistrationPage, customerRegistrationPageDef
5
5
  export { default as PagamioForgotPasswordPage, forgotPasswordDefaultText, } from './ForgotPasswordPage';
6
6
  export { default as PagamioResetPasswordPage, resetPasswordDefaultText, } from './ResetPasswordPage';
7
7
  export { AuthPageLayout as PagamioAuthPageLayout } from './AuthPageLayout';
8
+ export { FeatureCarousel } from './FeatureCarousel';
8
9
  export { default as OtpVerificationPage, OtpInput, otpVerificationDefaultText, } from './OtpVerification';
9
10
  export { passwordValidation } from './AuthFormUtils';
@@ -8,7 +8,6 @@ export interface MultiSelectProps {
8
8
  onChange?: (selected: string[]) => void;
9
9
  placeholder?: string;
10
10
  className?: string;
11
- onSearch?: (value: string) => void;
12
11
  field?: Field;
13
12
  tagPosition?: 'inside' | 'bottom';
14
13
  }
@@ -1,60 +1,103 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import * as Checkbox from '@radix-ui/react-checkbox';
3
- import { CheckIcon, ChevronDownIcon, Cross2Icon } from '@radix-ui/react-icons';
4
- import * as Popover from '@radix-ui/react-popover';
5
- import { forwardRef, useEffect, useState } from 'react';
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { ChevronDownIcon, Cross2Icon } from '@radix-ui/react-icons';
3
+ import { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
6
4
  import { cn } from '../../helpers';
7
5
  import Button from './Button';
8
- const TagsList = ({ selectedOptions, placeholder, removeTag }) => {
9
- if (selectedOptions.length === 0) {
10
- return _jsx("span", { className: "truncate", children: placeholder ?? 'Select options' });
11
- }
12
- return (_jsx("div", { className: "flex flex-wrap gap-2", children: selectedOptions.map((option) => (_jsxs("div", { className: "flex items-center gap-1 px-2 py-1 text-sm bg-muted rounded-md", children: [option.label, _jsx(Button, { type: "button", size: "icon", variant: "ghost", onClick: (e) => removeTag(option.value, e), className: "p-0.5 hover:bg-gray-200 rounded-full", children: _jsx(Cross2Icon, { className: "w-3 h-3" }) })] }, option.value))) }));
13
- };
14
- const MultiSelect = forwardRef(({ options, value, defaultValue = [], disabled, onChange, placeholder, className, onSearch, field, tagPosition = 'inside', }, ref) => {
6
+ const MultiSelect = forwardRef(({ options, value, defaultValue = [], disabled, onChange, placeholder, className, field, tagPosition = 'inside' }, ref) => {
15
7
  const [selectedValues, setSelectedValues] = useState(defaultValue);
16
- const [searchTerm, setSearchTerm] = useState('');
17
- const [filteredOptions, setFilteredOptions] = useState(options);
18
8
  const [isOpen, setIsOpen] = useState(false);
9
+ const [dropdownPos, setDropdownPos] = useState(null);
10
+ const containerRef = useRef(null);
11
+ const triggerRef = useRef(null);
12
+ // Sync external value changes (e.g. form reset / initial values)
19
13
  useEffect(() => {
20
- if (onSearch) {
21
- onSearch(searchTerm);
14
+ if (value !== undefined) {
15
+ setSelectedValues(value);
22
16
  }
23
- else {
24
- setFilteredOptions(options.filter((option) => option.label.toLowerCase().includes(searchTerm.toLowerCase())));
25
- }
26
- }, [searchTerm, options, onSearch]);
27
- const internalValues = value || selectedValues;
28
- const handleToggle = (option) => {
29
- const updatedValues = internalValues.includes(option)
30
- ? internalValues.filter((val) => val !== option)
31
- : [...internalValues, option];
32
- if (!value) {
33
- setSelectedValues(updatedValues);
34
- }
35
- onChange?.(updatedValues);
17
+ }, [value]);
18
+ // Measure trigger position so the fixed dropdown aligns correctly
19
+ const updateDropdownPosition = useCallback(() => {
20
+ if (!triggerRef.current)
21
+ return;
22
+ const rect = triggerRef.current.getBoundingClientRect();
23
+ setDropdownPos({
24
+ top: rect.bottom + window.scrollY + 4,
25
+ left: rect.left + window.scrollX,
26
+ width: rect.width,
27
+ });
28
+ }, []);
29
+ // Reposition on scroll / resize while open
30
+ useEffect(() => {
31
+ if (!isOpen)
32
+ return;
33
+ updateDropdownPosition();
34
+ window.addEventListener('scroll', updateDropdownPosition, true);
35
+ window.addEventListener('resize', updateDropdownPosition);
36
+ return () => {
37
+ window.removeEventListener('scroll', updateDropdownPosition, true);
38
+ window.removeEventListener('resize', updateDropdownPosition);
39
+ };
40
+ }, [isOpen, updateDropdownPosition]);
41
+ // Close on outside click
42
+ useEffect(() => {
43
+ if (!isOpen)
44
+ return;
45
+ const handleOutside = (e) => {
46
+ const target = e.target;
47
+ const clickedInsideTrigger = containerRef.current?.contains(target);
48
+ const dropdown = document.getElementById('ms-dropdown-portal');
49
+ const clickedInsideDropdown = dropdown?.contains(target);
50
+ if (!clickedInsideTrigger && !clickedInsideDropdown) {
51
+ setIsOpen(false);
52
+ }
53
+ };
54
+ document.addEventListener('mousedown', handleOutside);
55
+ return () => document.removeEventListener('mousedown', handleOutside);
56
+ }, [isOpen]);
57
+ const internalValues = value ?? selectedValues;
58
+ const handleToggle = (optionValue) => {
59
+ const updated = internalValues.includes(optionValue)
60
+ ? internalValues.filter((v) => v !== optionValue)
61
+ : [...internalValues, optionValue];
62
+ if (value === undefined)
63
+ setSelectedValues(updated);
64
+ onChange?.(updated);
36
65
  };
37
66
  const removeTag = (optionValue, e) => {
38
67
  e.stopPropagation();
39
68
  handleToggle(optionValue);
40
69
  };
41
- const selectedOptions = options.filter((option) => internalValues.includes(option.value));
42
- return (_jsx("div", { ref: ref, className: cn('relative w-full', className), children: _jsxs(Popover.Root, { open: isOpen, onOpenChange: setIsOpen, children: [_jsx(Popover.Trigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "ghost", disabled: disabled, className: "w-full cursor-pointer disabled:text-muted-foreground disabled:bg-muted disabled:cursor-not-allowed", onClick: (e) => {
43
- e.preventDefault();
44
- setIsOpen(!isOpen);
45
- }, onKeyDown: (e) => {
46
- if (e.key === 'Enter' || e.key === ' ') {
47
- e.preventDefault();
48
- setIsOpen(!isOpen);
49
- }
50
- }, children: [_jsxs("div", { id: field?.name, className: "flex items-center w-full px-4 py-2 border rounded-md", style: { minHeight: '41px' }, children: [_jsx("div", { className: "flex-1 text-left", children: tagPosition === 'inside' ? (_jsx(TagsList, { selectedOptions: selectedOptions, removeTag: removeTag, placeholder: field?.placeholder })) : (_jsx("span", { className: "truncate", children: selectedOptions.length === 0
51
- ? (field?.placeholder ?? placeholder ?? 'Select options')
52
- : `${selectedOptions.length} selected` })) }), _jsx(ChevronDownIcon, { className: "ml-2 flex-shrink-0" })] }), tagPosition === 'bottom' && selectedOptions.length > 0 && (_jsx("div", { className: "mt-2", children: _jsx(TagsList, { selectedOptions: selectedOptions, removeTag: removeTag, placeholder: field?.placeholder }) }))] }) }), _jsxs(Popover.Content, { className: "p-2 bg-popover text-popover-foreground border rounded shadow-md w-full z-50", align: "start", sideOffset: 5, onOpenAutoFocus: (e) => e.preventDefault(), children: [_jsx("input", { type: "text", placeholder: "Search...", value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), className: "w-full px-2 py-1 mb-2 border rounded", onClick: (e) => e.stopPropagation() }), _jsx("div", { className: "max-h-60 overflow-auto", children: filteredOptions.length > 0 ? (filteredOptions.map((option) => (_jsxs("div", { className: "flex items-center gap-2 px-2 py-1 cursor-pointer hover:bg-accent", children: [_jsx(Checkbox.Root, { type: "button", checked: internalValues.includes(option.value), onCheckedChange: () => handleToggle(option.value), className: "w-4 h-4 border rounded", children: _jsx(Checkbox.Indicator, { children: _jsx(CheckIcon, { className: "w-4 h-4 text-primary" }) }) }), _jsx(Button, { type: "button", variant: "ghost", onClick: () => handleToggle(option.value), onKeyDown: (e) => {
53
- if (e.key === 'Enter' || e.key === ' ') {
54
- e.preventDefault();
55
- handleToggle(option.value);
56
- }
57
- }, className: "text-left w-full", children: option.label })] }, option.value)))) : (_jsx("div", { className: "px-2 py-1 text-muted-foreground", children: "No options found" })) })] })] }) }));
70
+ const handleTriggerClick = () => {
71
+ if (disabled)
72
+ return;
73
+ if (!isOpen)
74
+ updateDropdownPosition();
75
+ setIsOpen((o) => !o);
76
+ };
77
+ const selectedOptions = options.filter((o) => internalValues.includes(o.value));
78
+ const displayPlaceholder = field?.placeholder ?? placeholder ?? 'Select options';
79
+ const tags = (_jsx(_Fragment, { children: selectedOptions.map((opt) => (_jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 rounded-md text-xs font-medium bg-primary/10 text-primary", children: [opt.label, _jsx(Button, { type: "button", variant: "ghost", size: "icon", "aria-label": `Remove ${opt.label}`, onClick: (e) => removeTag(opt.value, e), className: "h-4 w-4 p-0 hover:bg-primary/20 rounded-full", children: _jsx(Cross2Icon, { className: "w-2.5 h-2.5" }) })] }, opt.value))) }));
80
+ return (_jsxs("div", { ref: containerRef, className: cn('relative w-full', className), children: [_jsxs("button", { ref: triggerRef, type: "button", disabled: disabled, onClick: handleTriggerClick, onKeyDown: (e) => {
81
+ if (e.key === 'Enter' || e.key === ' ') {
82
+ e.preventDefault();
83
+ handleTriggerClick();
84
+ }
85
+ if (e.key === 'Escape')
86
+ setIsOpen(false);
87
+ }, className: cn('flex items-center w-full px-3 py-2 text-sm border border-input rounded-md bg-background text-left', 'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-0', 'transition-colors duration-150 min-h-[38px]', disabled
88
+ ? 'opacity-50 cursor-not-allowed bg-muted text-muted-foreground'
89
+ : 'cursor-pointer hover:border-primary/60', isOpen && 'border-primary ring-2 ring-ring'), "aria-haspopup": "listbox", "aria-expanded": isOpen, children: [_jsx("span", { className: "flex-1 min-w-0", children: tagPosition === 'inside' && selectedOptions.length > 0 ? (_jsx("span", { className: "flex flex-wrap gap-1", children: tags })) : (_jsx("span", { className: cn('truncate block', selectedOptions.length === 0 && 'text-muted-foreground'), children: selectedOptions.length === 0 || tagPosition === 'inside'
90
+ ? displayPlaceholder
91
+ : `${selectedOptions.length} selected` })) }), _jsx(ChevronDownIcon, { className: cn('ml-2 flex-shrink-0 text-muted-foreground transition-transform duration-150', isOpen && 'rotate-180') })] }), tagPosition === 'bottom' && selectedOptions.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-1 mt-2", children: tags })), isOpen && dropdownPos && (_jsx("div", { ref: ref, id: "ms-dropdown-portal", style: {
92
+ position: 'fixed',
93
+ top: dropdownPos.top,
94
+ left: dropdownPos.left,
95
+ width: dropdownPos.width,
96
+ }, className: cn('z-[9999]', 'bg-popover border border-border rounded-md shadow-lg', 'animate-in fade-in-0 zoom-in-95 duration-100'), children: _jsx("ul", { className: "max-h-56 overflow-y-auto p-1.5 space-y-0.5", children: options.length > 0 ? (options.map((option) => {
97
+ const checked = internalValues.includes(option.value);
98
+ const id = `ms-opt-${field?.name ?? 'ms'}-${option.value}`;
99
+ return (_jsx("li", { children: _jsxs("label", { htmlFor: id, className: cn('flex items-center gap-2.5 px-2 py-2 rounded-md cursor-pointer text-sm', 'hover:bg-accent hover:text-accent-foreground transition-colors', checked && 'bg-primary/5'), children: [_jsx("input", { id: id, type: "checkbox", checked: checked, onChange: () => handleToggle(option.value), className: cn('w-4 h-4 rounded border border-input bg-background flex-shrink-0', 'accent-[hsl(var(--primary))] cursor-pointer', 'focus:ring-2 focus:ring-ring focus:ring-offset-0') }), _jsx("span", { className: "flex-1 font-medium text-foreground", children: option.label })] }) }, option.value));
100
+ })) : (_jsx("li", { className: "px-3 py-2 text-sm text-muted-foreground text-center", children: "No options found" })) }) }))] }));
58
101
  });
59
102
  MultiSelect.displayName = 'MultiSelect';
60
103
  export default MultiSelect;
package/lib/styles.css CHANGED
@@ -842,6 +842,9 @@ video {
842
842
  .z-\[70\] {
843
843
  z-index: 70;
844
844
  }
845
+ .z-\[9999\] {
846
+ z-index: 9999;
847
+ }
845
848
  .z-auto {
846
849
  z-index: auto;
847
850
  }
@@ -1243,8 +1246,8 @@ video {
1243
1246
  .max-h-16 {
1244
1247
  max-height: 4rem;
1245
1248
  }
1246
- .max-h-60 {
1247
- max-height: 15rem;
1249
+ .max-h-56 {
1250
+ max-height: 14rem;
1248
1251
  }
1249
1252
  .max-h-64 {
1250
1253
  max-height: 16rem;
@@ -1279,6 +1282,9 @@ video {
1279
1282
  .min-h-\[200px\] {
1280
1283
  min-height: 200px;
1281
1284
  }
1285
+ .min-h-\[38px\] {
1286
+ min-height: 38px;
1287
+ }
1282
1288
  .min-h-\[60px\] {
1283
1289
  min-height: 60px;
1284
1290
  }
@@ -1727,6 +1733,9 @@ video {
1727
1733
  .gap-2 {
1728
1734
  gap: 0.5rem;
1729
1735
  }
1736
+ .gap-2\.5 {
1737
+ gap: 0.625rem;
1738
+ }
1730
1739
  .gap-3 {
1731
1740
  gap: 0.75rem;
1732
1741
  }
@@ -2638,9 +2647,6 @@ video {
2638
2647
  .p-0 {
2639
2648
  padding: 0px;
2640
2649
  }
2641
- .p-0\.5 {
2642
- padding: 0.125rem;
2643
- }
2644
2650
  .p-1 {
2645
2651
  padding: 0.25rem;
2646
2652
  }
@@ -3311,6 +3317,9 @@ video {
3311
3317
  --tw-placeholder-opacity: 1;
3312
3318
  color: rgb(142 75 16 / var(--tw-placeholder-opacity, 1));
3313
3319
  }
3320
+ .accent-\[hsl\(var\(--primary\)\)\] {
3321
+ accent-color: hsl(var(--primary));
3322
+ }
3314
3323
  .opacity-0 {
3315
3324
  opacity: 0;
3316
3325
  }
@@ -3422,6 +3431,9 @@ video {
3422
3431
  --tw-ring-opacity: 1;
3423
3432
  --tw-ring-color: rgb(240 82 82 / var(--tw-ring-opacity, 1));
3424
3433
  }
3434
+ .ring-ring {
3435
+ --tw-ring-color: hsl(var(--ring));
3436
+ }
3425
3437
  .ring-white {
3426
3438
  --tw-ring-opacity: 1;
3427
3439
  --tw-ring-color: rgb(255 255 255 / var(--tw-ring-opacity, 1));
@@ -3919,6 +3931,9 @@ video {
3919
3931
  --tw-border-opacity: 1;
3920
3932
  border-color: rgb(23 14 49 / var(--tw-border-opacity, 1));
3921
3933
  }
3934
+ .hover\:border-primary\/60:hover {
3935
+ border-color: hsl(var(--primary) / 0.6);
3936
+ }
3922
3937
  .hover\:border-purple-800:hover {
3923
3938
  --tw-border-opacity: 1;
3924
3939
  border-color: rgb(85 33 181 / var(--tw-border-opacity, 1));
@@ -4042,6 +4057,9 @@ video {
4042
4057
  .hover\:bg-primary\/15:hover {
4043
4058
  background-color: hsl(var(--primary) / 0.15);
4044
4059
  }
4060
+ .hover\:bg-primary\/20:hover {
4061
+ background-color: hsl(var(--primary) / 0.2);
4062
+ }
4045
4063
  .hover\:bg-primary\/90:hover {
4046
4064
  background-color: hsl(var(--primary) / 0.9);
4047
4065
  }
@@ -4446,6 +4464,9 @@ video {
4446
4464
  --tw-ring-opacity: 1;
4447
4465
  --tw-ring-color: rgb(194 120 3 / var(--tw-ring-opacity, 1));
4448
4466
  }
4467
+ .focus\:ring-offset-0:focus {
4468
+ --tw-ring-offset-width: 0px;
4469
+ }
4449
4470
  .focus\:ring-offset-2:focus {
4450
4471
  --tw-ring-offset-width: 2px;
4451
4472
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pagamio/frontend-commons-lib",
3
3
  "description": "Pagamio library for Frontend reusable components like the form engine and table container",
4
- "version": "0.8.253",
4
+ "version": "0.8.255",
5
5
  "publishConfig": {
6
6
  "access": "public",
7
7
  "provenance": false