flysoft-react-ui 0.3.1 → 0.3.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.
- package/dist/components/form-controls/AutocompleteInput.d.ts +39 -0
- package/dist/components/form-controls/AutocompleteInput.d.ts.map +1 -0
- package/dist/components/form-controls/AutocompleteInput.js +113 -0
- package/dist/components/form-controls/DateInput.d.ts +12 -0
- package/dist/components/form-controls/DateInput.d.ts.map +1 -0
- package/dist/components/form-controls/DateInput.js +156 -0
- package/dist/components/form-controls/DatePicker.d.ts +14 -0
- package/dist/components/form-controls/DatePicker.d.ts.map +1 -0
- package/dist/components/form-controls/DatePicker.js +148 -0
- package/dist/components/form-controls/index.d.ts +6 -0
- package/dist/components/form-controls/index.d.ts.map +1 -1
- package/dist/components/form-controls/index.js +3 -0
- package/dist/components/layout/AppLayout.d.ts.map +1 -1
- package/dist/components/layout/AppLayout.js +39 -5
- package/dist/docs/AutocompleteInputDocs.d.ts +4 -0
- package/dist/docs/AutocompleteInputDocs.d.ts.map +1 -0
- package/dist/docs/AutocompleteInputDocs.js +75 -0
- package/dist/docs/DateInputDocs.d.ts +4 -0
- package/dist/docs/DateInputDocs.d.ts.map +1 -0
- package/dist/docs/DateInputDocs.js +21 -0
- package/dist/docs/DatePickerDocs.d.ts +4 -0
- package/dist/docs/DatePickerDocs.d.ts.map +1 -0
- package/dist/docs/DatePickerDocs.js +18 -0
- package/dist/docs/DocsMenu.d.ts.map +1 -1
- package/dist/docs/DocsMenu.js +1 -1
- package/dist/docs/DocsRouter.d.ts.map +1 -1
- package/dist/docs/DocsRouter.js +4 -1
- package/dist/hooks/useElementScroll.d.ts.map +1 -1
- package/dist/hooks/useElementScroll.js +37 -15
- package/dist/index.css +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { InputProps } from "./Input";
|
|
3
|
+
export interface AutocompleteOption {
|
|
4
|
+
label: string;
|
|
5
|
+
value: string;
|
|
6
|
+
description?: string | number;
|
|
7
|
+
icon?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface AutocompleteInputProps<T = AutocompleteOption, K = string> extends Omit<InputProps, "onChange" | "value"> {
|
|
10
|
+
options: T[];
|
|
11
|
+
value?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Valor de texto del input (controlado)
|
|
14
|
+
*/
|
|
15
|
+
onChange?: (value: string) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Callback al seleccionar una opción. Devuelve el item completo (T) y el valor mapeado (K)
|
|
18
|
+
*/
|
|
19
|
+
onSelectOption?: (option: T, value: K) => void;
|
|
20
|
+
noResultsText?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Obtiene el label que se muestra para cada opción. Por defecto usa la propiedad "label".
|
|
23
|
+
*/
|
|
24
|
+
getOptionLabel?: (item: T) => string;
|
|
25
|
+
/**
|
|
26
|
+
* Obtiene el valor que se devuelve al seleccionar una opción. Por defecto usa la propiedad "value".
|
|
27
|
+
*/
|
|
28
|
+
getOptionValue?: (item: T) => K;
|
|
29
|
+
/**
|
|
30
|
+
* Obtiene la descripción opcional para cada opción. Por defecto usa la propiedad "description".
|
|
31
|
+
*/
|
|
32
|
+
getOptionDescription?: (item: T) => string | number | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Renderizado personalizado de cada opción. Si se define, se ignora el render por defecto.
|
|
35
|
+
*/
|
|
36
|
+
renderOption?: (item: T) => React.ReactNode;
|
|
37
|
+
}
|
|
38
|
+
export declare const AutocompleteInput: <T = AutocompleteOption, K = string>({ options, value, onChange, onSelectOption, noResultsText, className, getOptionLabel, getOptionValue, getOptionDescription, renderOption, ...inputProps }: AutocompleteInputProps<T, K>) => import("react/jsx-runtime").JSX.Element;
|
|
39
|
+
//# sourceMappingURL=AutocompleteInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AutocompleteInput.d.ts","sourceRoot":"","sources":["../../../src/components/form-controls/AutocompleteInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAG1C,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,sBAAsB,CAAC,CAAC,GAAG,kBAAkB,EAAE,CAAC,GAAG,MAAM,CACxE,SAAQ,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC;IAC9C,OAAO,EAAE,CAAC,EAAE,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC;;OAEG;IACH,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC/C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC;IACrC;;OAEG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC;IAChC;;OAEG;IACH,oBAAoB,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAChE;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,KAAK,CAAC,SAAS,CAAC;CAC7C;AAED,eAAO,MAAM,iBAAiB,GAAI,CAAC,GAAG,kBAAkB,EAAE,CAAC,GAAG,MAAM,EAAE,2JAYnE,sBAAsB,CAAC,CAAC,EAAE,CAAC,CAAC,4CAoM9B,CAAC"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Input } from "./Input";
|
|
4
|
+
export const AutocompleteInput = ({ options, value, onChange, onSelectOption, noResultsText = "Sin resultados", className = "", getOptionLabel, getOptionValue, getOptionDescription, renderOption, ...inputProps }) => {
|
|
5
|
+
const [internalValue, setInternalValue] = React.useState(value || "");
|
|
6
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
7
|
+
const [highlightedIndex, setHighlightedIndex] = React.useState(-1);
|
|
8
|
+
const containerRef = React.useRef(null);
|
|
9
|
+
const inputValue = value !== undefined ? value : internalValue;
|
|
10
|
+
const labelGetter = React.useCallback((item) => {
|
|
11
|
+
if (getOptionLabel)
|
|
12
|
+
return getOptionLabel(item);
|
|
13
|
+
const anyItem = item;
|
|
14
|
+
return (anyItem.label ?? "").toString();
|
|
15
|
+
}, [getOptionLabel]);
|
|
16
|
+
const valueGetter = React.useCallback((item) => {
|
|
17
|
+
if (getOptionValue)
|
|
18
|
+
return getOptionValue(item);
|
|
19
|
+
const anyItem = item;
|
|
20
|
+
return anyItem.value ?? undefined;
|
|
21
|
+
}, [getOptionValue]);
|
|
22
|
+
const descriptionGetter = React.useCallback((item) => {
|
|
23
|
+
if (getOptionDescription)
|
|
24
|
+
return getOptionDescription(item);
|
|
25
|
+
const anyItem = item;
|
|
26
|
+
return anyItem.description;
|
|
27
|
+
}, [getOptionDescription]);
|
|
28
|
+
const filteredOptions = React.useMemo(() => {
|
|
29
|
+
const search = inputValue.trim().toLowerCase();
|
|
30
|
+
if (!search)
|
|
31
|
+
return options;
|
|
32
|
+
return options.filter((option) => {
|
|
33
|
+
const label = labelGetter(option).toLowerCase();
|
|
34
|
+
const optionValue = String(valueGetter(option) ?? "").toLowerCase();
|
|
35
|
+
return label.includes(search) || optionValue.includes(search);
|
|
36
|
+
});
|
|
37
|
+
}, [inputValue, options, labelGetter, valueGetter]);
|
|
38
|
+
const handleChange = (event) => {
|
|
39
|
+
const newValue = event.target.value;
|
|
40
|
+
if (value === undefined) {
|
|
41
|
+
setInternalValue(newValue);
|
|
42
|
+
}
|
|
43
|
+
onChange?.(newValue);
|
|
44
|
+
setIsOpen(true);
|
|
45
|
+
setHighlightedIndex(-1);
|
|
46
|
+
};
|
|
47
|
+
const handleSelect = (option) => {
|
|
48
|
+
const label = labelGetter(option);
|
|
49
|
+
const selectedValue = valueGetter(option);
|
|
50
|
+
if (value === undefined) {
|
|
51
|
+
setInternalValue(label);
|
|
52
|
+
}
|
|
53
|
+
onChange?.(label);
|
|
54
|
+
onSelectOption?.(option, selectedValue);
|
|
55
|
+
setIsOpen(false);
|
|
56
|
+
};
|
|
57
|
+
const handleKeyDown = (event) => {
|
|
58
|
+
if (!isOpen && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
|
|
59
|
+
setIsOpen(true);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (!filteredOptions.length)
|
|
63
|
+
return;
|
|
64
|
+
if (event.key === "ArrowDown") {
|
|
65
|
+
event.preventDefault();
|
|
66
|
+
setHighlightedIndex((prev) => prev < filteredOptions.length - 1 ? prev + 1 : 0);
|
|
67
|
+
}
|
|
68
|
+
else if (event.key === "ArrowUp") {
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
setHighlightedIndex((prev) => prev > 0 ? prev - 1 : filteredOptions.length - 1);
|
|
71
|
+
}
|
|
72
|
+
else if (event.key === "Enter") {
|
|
73
|
+
if (highlightedIndex >= 0 && highlightedIndex < filteredOptions.length) {
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
handleSelect(filteredOptions[highlightedIndex]);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else if (event.key === "Escape") {
|
|
79
|
+
setIsOpen(false);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
React.useEffect(() => {
|
|
83
|
+
const handleClickOutside = (event) => {
|
|
84
|
+
if (containerRef.current &&
|
|
85
|
+
!containerRef.current.contains(event.target)) {
|
|
86
|
+
setIsOpen(false);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
90
|
+
return () => {
|
|
91
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
92
|
+
};
|
|
93
|
+
}, []);
|
|
94
|
+
React.useEffect(() => {
|
|
95
|
+
if (value !== undefined) {
|
|
96
|
+
setInternalValue(value);
|
|
97
|
+
}
|
|
98
|
+
}, [value]);
|
|
99
|
+
const showDropdown = isOpen && (filteredOptions.length > 0 || noResultsText);
|
|
100
|
+
return (_jsxs("div", { ref: containerRef, className: "relative w-full", children: [_jsx(Input, { ...inputProps, value: inputValue, onChange: handleChange, onFocus: () => setIsOpen(true), onKeyDown: handleKeyDown, className: className, autoComplete: "off" }), showDropdown && (_jsx("div", { className: "absolute z-20 mt-1 w-full rounded-md border border-[var(--color-border-default)] \r\n bg-[var(--color-bg-default)] shadow-[var(--shadow-lg)] max-h-60 overflow-auto", children: filteredOptions.length > 0 ? (_jsx("ul", { className: "py-1", children: filteredOptions.map((option, index) => {
|
|
101
|
+
const label = labelGetter(option);
|
|
102
|
+
const description = descriptionGetter(option);
|
|
103
|
+
const anyOption = option;
|
|
104
|
+
return (_jsx("li", { className: `px-3 py-2 cursor-pointer flex items-start gap-2 text-sm
|
|
105
|
+
${index === highlightedIndex
|
|
106
|
+
? "bg-[var(--color-primary-soft)] text-[var(--color-primary)]"
|
|
107
|
+
: "text-[var(--color-text-primary)] hover:bg-[var(--color-bg-secondary)]"}`, onMouseDown: (event) => {
|
|
108
|
+
event.preventDefault();
|
|
109
|
+
handleSelect(option);
|
|
110
|
+
}, onMouseEnter: () => setHighlightedIndex(index), children: renderOption ? (renderOption(option)) : (_jsxs(_Fragment, { children: [anyOption.icon && (_jsx("i", { className: `fa ${anyOption.icon} mt-0.5 text-[var(--color-text-muted)]` })), _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { className: "font-[var(--font-default)]", children: label }), description !== undefined &&
|
|
111
|
+
description !== null && (_jsx("span", { className: "text-xs text-[var(--color-text-secondary)]", children: description }))] })] })) }, String(valueGetter(option) ?? label ?? index)));
|
|
112
|
+
}) })) : (_jsx("div", { className: "px-3 py-2 text-sm text-[var(--color-text-secondary)]", children: noResultsText })) }))] }));
|
|
113
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { InputProps } from "./Input";
|
|
3
|
+
import type { DatePickerProps } from "./DatePicker";
|
|
4
|
+
export type DateInputFormat = "dd/mm/yyyy" | "mm/dd/yyyy";
|
|
5
|
+
export interface DateInputProps extends Omit<InputProps, "type" | "value" | "onChange"> {
|
|
6
|
+
value?: Date | null;
|
|
7
|
+
onChange?: (date: Date | null) => void;
|
|
8
|
+
format?: DateInputFormat;
|
|
9
|
+
datePickerProps?: Omit<DatePickerProps, "value" | "onChange">;
|
|
10
|
+
}
|
|
11
|
+
export declare const DateInput: React.FC<DateInputProps>;
|
|
12
|
+
//# sourceMappingURL=DateInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DateInput.d.ts","sourceRoot":"","sources":["../../../src/components/form-controls/DateInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,YAAY,CAAC;AAE1D,MAAM,WAAW,cACf,SAAQ,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;IACvD,KAAK,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,CAAC;IACvC,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,eAAe,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,OAAO,GAAG,UAAU,CAAC,CAAC;CAC/D;AAsFD,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CAuK9C,CAAC"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Input } from "./Input";
|
|
4
|
+
import { DatePicker } from "./DatePicker";
|
|
5
|
+
const pad = (value) => value.toString().padStart(2, "0");
|
|
6
|
+
const formatDateToString = (date, format) => {
|
|
7
|
+
if (!date)
|
|
8
|
+
return "";
|
|
9
|
+
const day = pad(date.getDate());
|
|
10
|
+
const month = pad(date.getMonth() + 1);
|
|
11
|
+
const year = date.getFullYear().toString();
|
|
12
|
+
if (format === "mm/dd/yyyy") {
|
|
13
|
+
return `${month}/${day}/${year}`;
|
|
14
|
+
}
|
|
15
|
+
return `${day}/${month}/${year}`;
|
|
16
|
+
};
|
|
17
|
+
const parseDateFromString = (value, format) => {
|
|
18
|
+
// Primero intentar parsear como números sin separadores (ej: 11102025)
|
|
19
|
+
const numbersOnly = value.replace(/\D/g, "");
|
|
20
|
+
if (numbersOnly.length === 8) {
|
|
21
|
+
// Formato: ddmmyyyy o mmddyyyy
|
|
22
|
+
const p1 = parseInt(numbersOnly.substring(0, 2), 10);
|
|
23
|
+
const p2 = parseInt(numbersOnly.substring(2, 4), 10);
|
|
24
|
+
const p3 = parseInt(numbersOnly.substring(4, 8), 10);
|
|
25
|
+
const day = format === "mm/dd/yyyy" ? p2 : p1;
|
|
26
|
+
const month = format === "mm/dd/yyyy" ? p1 : p2;
|
|
27
|
+
const year = p3;
|
|
28
|
+
if (!isNaN(day) &&
|
|
29
|
+
!isNaN(month) &&
|
|
30
|
+
!isNaN(year) &&
|
|
31
|
+
day >= 1 &&
|
|
32
|
+
month >= 1 &&
|
|
33
|
+
month <= 12 &&
|
|
34
|
+
year >= 1000 &&
|
|
35
|
+
year <= 9999) {
|
|
36
|
+
const date = new Date(year, month - 1, day);
|
|
37
|
+
if (date.getFullYear() === year &&
|
|
38
|
+
date.getMonth() === month - 1 &&
|
|
39
|
+
date.getDate() === day) {
|
|
40
|
+
return date;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Si no funciona, intentar parsear con separadores
|
|
45
|
+
const parts = value.split(/[/\-.]/).map((p) => p.trim());
|
|
46
|
+
if (parts.length !== 3)
|
|
47
|
+
return null;
|
|
48
|
+
const [p1, p2, p3] = parts;
|
|
49
|
+
const day = format === "mm/dd/yyyy" ? parseInt(p2, 10) : parseInt(p1, 10);
|
|
50
|
+
const month = format === "mm/dd/yyyy" ? parseInt(p1, 10) : parseInt(p2, 10);
|
|
51
|
+
const year = parseInt(p3, 10);
|
|
52
|
+
if (isNaN(day) ||
|
|
53
|
+
isNaN(month) ||
|
|
54
|
+
isNaN(year) ||
|
|
55
|
+
day < 1 ||
|
|
56
|
+
month < 1 ||
|
|
57
|
+
month > 12) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const date = new Date(year, month - 1, day);
|
|
61
|
+
if (date.getFullYear() !== year ||
|
|
62
|
+
date.getMonth() !== month - 1 ||
|
|
63
|
+
date.getDate() !== day) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return date;
|
|
67
|
+
};
|
|
68
|
+
export const DateInput = ({ value, onChange, format = "dd/mm/yyyy", datePickerProps, icon = "fa-calendar-alt", iconPosition = "right", className = "", ...inputProps }) => {
|
|
69
|
+
const [internalDate, setInternalDate] = React.useState(value ?? null);
|
|
70
|
+
const [inputValue, setInputValue] = React.useState(formatDateToString(value ?? null, format));
|
|
71
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
72
|
+
const containerRef = React.useRef(null);
|
|
73
|
+
const inputWrapperRef = React.useRef(null);
|
|
74
|
+
const iconRef = React.useRef(null);
|
|
75
|
+
React.useEffect(() => {
|
|
76
|
+
if (value !== undefined) {
|
|
77
|
+
setInternalDate(value);
|
|
78
|
+
setInputValue(formatDateToString(value, format));
|
|
79
|
+
}
|
|
80
|
+
}, [value, format]);
|
|
81
|
+
// Centrar el ícono verticalmente respecto al input real
|
|
82
|
+
React.useEffect(() => {
|
|
83
|
+
const updateIconPosition = () => {
|
|
84
|
+
if (iconPosition === "right" &&
|
|
85
|
+
inputWrapperRef.current &&
|
|
86
|
+
iconRef.current) {
|
|
87
|
+
const inputElement = inputWrapperRef.current.querySelector("input");
|
|
88
|
+
if (inputElement) {
|
|
89
|
+
const inputRect = inputElement.getBoundingClientRect();
|
|
90
|
+
const wrapperRect = inputWrapperRef.current.getBoundingClientRect();
|
|
91
|
+
const topOffset = inputRect.top - wrapperRect.top + inputRect.height / 2;
|
|
92
|
+
iconRef.current.style.top = `${topOffset}px`;
|
|
93
|
+
iconRef.current.style.transform = "translateY(-50%)";
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
// Ejecutar inmediatamente
|
|
98
|
+
updateIconPosition();
|
|
99
|
+
// Ejecutar cuando cambie el tamaño de la ventana
|
|
100
|
+
window.addEventListener("resize", updateIconPosition);
|
|
101
|
+
return () => {
|
|
102
|
+
window.removeEventListener("resize", updateIconPosition);
|
|
103
|
+
};
|
|
104
|
+
}, [iconPosition, inputValue, inputProps.label, inputProps.size]);
|
|
105
|
+
const handleDateChange = (date) => {
|
|
106
|
+
if (value === undefined) {
|
|
107
|
+
setInternalDate(date);
|
|
108
|
+
setInputValue(formatDateToString(date, format));
|
|
109
|
+
}
|
|
110
|
+
onChange?.(date);
|
|
111
|
+
setIsOpen(false);
|
|
112
|
+
};
|
|
113
|
+
const handleInputChange = (event) => {
|
|
114
|
+
const newValue = event.target.value;
|
|
115
|
+
setInputValue(newValue);
|
|
116
|
+
// No intentamos parsear en cada pulsación, solo actualizamos el texto.
|
|
117
|
+
};
|
|
118
|
+
const handleInputBlur = (event) => {
|
|
119
|
+
const newValue = event.target.value.trim();
|
|
120
|
+
if (!newValue) {
|
|
121
|
+
handleDateChange(null);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const parsed = parseDateFromString(newValue, format);
|
|
125
|
+
if (parsed) {
|
|
126
|
+
handleDateChange(parsed);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// Si no es válida, restauramos el valor anterior formateado.
|
|
130
|
+
setInputValue(formatDateToString(internalDate, format));
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
const handleIconClick = (event) => {
|
|
134
|
+
event.preventDefault();
|
|
135
|
+
setIsOpen((prev) => !prev);
|
|
136
|
+
};
|
|
137
|
+
React.useEffect(() => {
|
|
138
|
+
const handleClickOutside = (event) => {
|
|
139
|
+
if (containerRef.current &&
|
|
140
|
+
!containerRef.current.contains(event.target)) {
|
|
141
|
+
setIsOpen(false);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
145
|
+
return () => {
|
|
146
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
147
|
+
};
|
|
148
|
+
}, []);
|
|
149
|
+
const datePickerInitialViewDate = internalDate ?? datePickerProps?.initialViewDate ?? new Date();
|
|
150
|
+
return (_jsxs("div", { ref: containerRef, className: "relative w-full", children: [_jsxs("div", { ref: inputWrapperRef, className: "relative", children: [_jsx(Input, { ...inputProps, type: "text", value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, icon: iconPosition === "right" ? undefined : icon, iconPosition: iconPosition, placeholder: inputProps.placeholder ??
|
|
151
|
+
(format === "mm/dd/yyyy" ? "mm/dd/yyyy" : "dd/mm/yyyy"), className: `${className} ${iconPosition === "right" ? "pr-10" : ""}` }), iconPosition === "right" && (_jsx("div", { ref: iconRef, className: "absolute right-3 cursor-pointer", onMouseDown: handleIconClick, children: _jsx("i", { className: `fa ${icon} ${inputProps.size === "sm"
|
|
152
|
+
? "w-4 h-4"
|
|
153
|
+
: inputProps.size === "lg"
|
|
154
|
+
? "w-6 h-6"
|
|
155
|
+
: "w-5 h-5"} text-[var(--color-text-muted)]` }) }))] }), isOpen && (_jsx("div", { className: "absolute z-20 mt-1 right-0", children: _jsx(DatePicker, { ...datePickerProps, value: internalDate ?? datePickerInitialViewDate, onChange: (date) => handleDateChange(date) }) }))] }));
|
|
156
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export type DatePickerView = {
|
|
3
|
+
month: number;
|
|
4
|
+
year: number;
|
|
5
|
+
};
|
|
6
|
+
export interface DatePickerProps {
|
|
7
|
+
value?: Date | null;
|
|
8
|
+
onChange?: (date: Date) => void;
|
|
9
|
+
initialViewDate?: Date;
|
|
10
|
+
startWeekOn?: "monday" | "sunday";
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const DatePicker: React.FC<DatePickerProps>;
|
|
14
|
+
//# sourceMappingURL=DatePicker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DatePicker.d.ts","sourceRoot":"","sources":["../../../src/components/form-controls/DatePicker.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAChC,eAAe,CAAC,EAAE,IAAI,CAAC;IACvB,WAAW,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA4BD,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAuOhD,CAAC"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Button } from "./Button";
|
|
4
|
+
const createDateAtMidnight = (date) => {
|
|
5
|
+
const d = new Date(date);
|
|
6
|
+
d.setHours(0, 0, 0, 0);
|
|
7
|
+
return d;
|
|
8
|
+
};
|
|
9
|
+
const isSameDay = (a, b) => {
|
|
10
|
+
return (a.getFullYear() === b.getFullYear() &&
|
|
11
|
+
a.getMonth() === b.getMonth() &&
|
|
12
|
+
a.getDate() === b.getDate());
|
|
13
|
+
};
|
|
14
|
+
const getDaysInMonth = (year, month) => {
|
|
15
|
+
return new Date(year, month + 1, 0).getDate();
|
|
16
|
+
};
|
|
17
|
+
const getWeekdayLabels = (startWeekOn) => {
|
|
18
|
+
const base = ["D", "L", "M", "X", "J", "V", "S"];
|
|
19
|
+
if (startWeekOn === "sunday") {
|
|
20
|
+
return base;
|
|
21
|
+
}
|
|
22
|
+
return [...base.slice(1), base[0]];
|
|
23
|
+
};
|
|
24
|
+
export const DatePicker = ({ value, onChange, initialViewDate, startWeekOn = "sunday", className = "", }) => {
|
|
25
|
+
const today = React.useMemo(() => createDateAtMidnight(new Date()), []);
|
|
26
|
+
const initial = React.useMemo(() => {
|
|
27
|
+
const base = value ?? initialViewDate ?? today;
|
|
28
|
+
return {
|
|
29
|
+
month: base.getMonth(),
|
|
30
|
+
year: base.getFullYear(),
|
|
31
|
+
};
|
|
32
|
+
}, [value, initialViewDate, today]);
|
|
33
|
+
const [view, setView] = React.useState(initial);
|
|
34
|
+
React.useEffect(() => {
|
|
35
|
+
if (value) {
|
|
36
|
+
setView({
|
|
37
|
+
month: value.getMonth(),
|
|
38
|
+
year: value.getFullYear(),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}, [value]);
|
|
42
|
+
const handlePrevMonth = () => {
|
|
43
|
+
setView((prev) => {
|
|
44
|
+
const month = prev.month === 0 ? 11 : prev.month - 1;
|
|
45
|
+
const year = prev.month === 0 ? prev.year - 1 : prev.year;
|
|
46
|
+
return { month, year };
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
const handleNextMonth = () => {
|
|
50
|
+
setView((prev) => {
|
|
51
|
+
const month = prev.month === 11 ? 0 : prev.month + 1;
|
|
52
|
+
const year = prev.month === 11 ? prev.year + 1 : prev.year;
|
|
53
|
+
return { month, year };
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
const handlePrevYear = () => {
|
|
57
|
+
setView((prev) => ({ ...prev, year: prev.year - 1 }));
|
|
58
|
+
};
|
|
59
|
+
const handleNextYear = () => {
|
|
60
|
+
setView((prev) => ({ ...prev, year: prev.year + 1 }));
|
|
61
|
+
};
|
|
62
|
+
const handleSelectDay = (day, month, year) => {
|
|
63
|
+
const targetMonth = month !== undefined ? month : view.month;
|
|
64
|
+
const targetYear = year !== undefined ? year : view.year;
|
|
65
|
+
const date = new Date(targetYear, targetMonth, day);
|
|
66
|
+
onChange?.(createDateAtMidnight(date));
|
|
67
|
+
// Si el día es de otro mes, cambiar la vista
|
|
68
|
+
if (month !== undefined && month !== view.month) {
|
|
69
|
+
setView({ month: targetMonth, year: targetYear });
|
|
70
|
+
}
|
|
71
|
+
else if (year !== undefined && year !== view.year) {
|
|
72
|
+
setView({ month: targetMonth, year: targetYear });
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const firstDayOfMonth = new Date(view.year, view.month, 1);
|
|
76
|
+
const firstWeekday = firstDayOfMonth.getDay(); // 0-6, Sunday=0
|
|
77
|
+
const daysInMonth = getDaysInMonth(view.year, view.month);
|
|
78
|
+
const weekdayLabels = getWeekdayLabels(startWeekOn);
|
|
79
|
+
const offset = startWeekOn === "sunday"
|
|
80
|
+
? firstWeekday
|
|
81
|
+
: firstWeekday === 0
|
|
82
|
+
? 6
|
|
83
|
+
: firstWeekday - 1;
|
|
84
|
+
// Calcular días del mes anterior y siguiente
|
|
85
|
+
const prevMonth = view.month === 0 ? 11 : view.month - 1;
|
|
86
|
+
const prevYear = view.month === 0 ? view.year - 1 : view.year;
|
|
87
|
+
const daysInPrevMonth = getDaysInMonth(prevYear, prevMonth);
|
|
88
|
+
const nextMonth = view.month === 11 ? 0 : view.month + 1;
|
|
89
|
+
const nextYear = view.month === 11 ? view.year + 1 : view.year;
|
|
90
|
+
const weeks = [];
|
|
91
|
+
// Construir todas las semanas (6 semanas = 42 días)
|
|
92
|
+
let dayCounter = 1 - offset; // Puede ser negativo para días del mes anterior
|
|
93
|
+
for (let week = 0; week < 6; week++) {
|
|
94
|
+
const currentWeek = [];
|
|
95
|
+
for (let dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {
|
|
96
|
+
if (dayCounter < 1) {
|
|
97
|
+
// Día del mes anterior
|
|
98
|
+
const day = daysInPrevMonth + dayCounter;
|
|
99
|
+
currentWeek.push({ day, month: prevMonth, year: prevYear });
|
|
100
|
+
}
|
|
101
|
+
else if (dayCounter > daysInMonth) {
|
|
102
|
+
// Día del mes siguiente
|
|
103
|
+
const day = dayCounter - daysInMonth;
|
|
104
|
+
currentWeek.push({ day, month: nextMonth, year: nextYear });
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
// Día del mes actual
|
|
108
|
+
currentWeek.push({
|
|
109
|
+
day: dayCounter,
|
|
110
|
+
month: view.month,
|
|
111
|
+
year: view.year,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
dayCounter++;
|
|
115
|
+
}
|
|
116
|
+
weeks.push(currentWeek);
|
|
117
|
+
}
|
|
118
|
+
const selectedDate = value && !isNaN(value.getTime()) ? createDateAtMidnight(value) : null;
|
|
119
|
+
const monthName = new Date(view.year, view.month, 1).toLocaleString("es-ES", {
|
|
120
|
+
month: "long",
|
|
121
|
+
});
|
|
122
|
+
return (_jsxs("div", { className: `inline-flex flex-col rounded-lg border border-[var(--color-border-default)]
|
|
123
|
+
bg-[var(--color-bg-default)] p-3 shadow-sm font-[var(--font-default)] text-sm ${className}`, children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Button, { size: "sm", variant: "ghost", icon: "fa-angle-double-left", onClick: handlePrevYear, "aria-label": "A\u00F1o anterior" }), _jsx(Button, { size: "sm", variant: "ghost", icon: "fa-angle-left", onClick: handlePrevMonth, "aria-label": "Mes anterior" })] }), _jsx("div", { className: "text-center", children: _jsxs("div", { className: "text-[var(--color-text-primary)] font-medium capitalize", children: [monthName, " ", view.year] }) }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Button, { size: "sm", variant: "ghost", icon: "fa-angle-right", onClick: handleNextMonth, "aria-label": "Mes siguiente" }), _jsx(Button, { size: "sm", variant: "ghost", icon: "fa-angle-double-right", onClick: handleNextYear, "aria-label": "A\u00F1o siguiente" })] })] }), _jsx("div", { className: "grid grid-cols-7 gap-1 mb-1", children: weekdayLabels.map((label) => (_jsx("div", { className: "text-xs text-center text-[var(--color-text-secondary)]", children: label }, label))) }), _jsx("div", { className: "grid grid-rows-6 gap-1", children: weeks.map((week, rowIndex) => (_jsx("div", { className: "grid grid-cols-7 gap-1", children: week.map((dayInfo, index) => {
|
|
124
|
+
const { day, month, year } = dayInfo;
|
|
125
|
+
const isCurrentMonth = month === view.month && year === view.year;
|
|
126
|
+
const date = new Date(year, month, day);
|
|
127
|
+
const isToday = isSameDay(date, today);
|
|
128
|
+
const isSelected = selectedDate !== null && isSameDay(date, selectedDate);
|
|
129
|
+
let dayClasses = "w-8 h-8 flex items-center justify-center rounded-full cursor-pointer text-xs";
|
|
130
|
+
if (isSelected) {
|
|
131
|
+
dayClasses +=
|
|
132
|
+
" bg-[var(--color-primary)] text-[var(--color-primary-contrast)]";
|
|
133
|
+
}
|
|
134
|
+
else if (isToday) {
|
|
135
|
+
dayClasses +=
|
|
136
|
+
" border border-[var(--color-primary)] text-[var(--color-primary)]";
|
|
137
|
+
}
|
|
138
|
+
else if (isCurrentMonth) {
|
|
139
|
+
dayClasses +=
|
|
140
|
+
" text-[var(--color-text-primary)] hover:bg-[var(--color-bg-secondary)]";
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
dayClasses +=
|
|
144
|
+
" text-[var(--color-text-muted)] opacity-50 hover:bg-[var(--color-bg-secondary)] hover:opacity-75";
|
|
145
|
+
}
|
|
146
|
+
return (_jsx("button", { type: "button", className: dayClasses, onClick: () => handleSelectDay(day, month, year), children: day }, index));
|
|
147
|
+
}) }, rowIndex))) })] }));
|
|
148
|
+
};
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
export { Button } from "./Button";
|
|
2
2
|
export { Input } from "./Input";
|
|
3
|
+
export { AutocompleteInput } from "./AutocompleteInput";
|
|
4
|
+
export { DatePicker } from "./DatePicker";
|
|
5
|
+
export { DateInput } from "./DateInput";
|
|
3
6
|
export type { ButtonProps } from "./Button";
|
|
4
7
|
export type { InputProps } from "./Input";
|
|
8
|
+
export type { AutocompleteInputProps, AutocompleteOption, } from "./AutocompleteInput";
|
|
9
|
+
export type { DatePickerProps } from "./DatePicker";
|
|
10
|
+
export type { DateInputProps, DateInputFormat } from "./DateInput";
|
|
5
11
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/form-controls/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,YAAY,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/form-controls/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,YAAY,EACV,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppLayout.d.ts","sourceRoot":"","sources":["../../../src/components/layout/AppLayout.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA2B,MAAM,OAAO,CAAC;AAKhD,MAAM,WAAW,cAAc;IAC7B,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC/B,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,
|
|
1
|
+
{"version":3,"file":"AppLayout.d.ts","sourceRoot":"","sources":["../../../src/components/layout/AppLayout.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA2B,MAAM,OAAO,CAAC;AAKhD,MAAM,WAAW,cAAc;IAC7B,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC/B,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CAyN9C,CAAC"}
|
|
@@ -10,35 +10,69 @@ export const AppLayout = ({ navBarDrawer, leftDrawer, children, className = "",
|
|
|
10
10
|
const [isMobileDrawerOpen, setIsMobileDrawerOpen] = useState(false);
|
|
11
11
|
const [isNavbarVisible, setIsNavbarVisible] = useState(true);
|
|
12
12
|
const isNavbarVisibleRef = useRef(isNavbarVisible);
|
|
13
|
+
const isTransitioningRef = useRef(false);
|
|
14
|
+
const lastScrollYRef = useRef(0);
|
|
13
15
|
const shouldShowMobileDrawer = isMobile || isTablet;
|
|
14
16
|
const shouldShowDesktopDrawer = !shouldShowMobileDrawer && leftDrawer;
|
|
15
17
|
// Mantener el ref sincronizado con el estado
|
|
16
18
|
React.useEffect(() => {
|
|
17
19
|
isNavbarVisibleRef.current = isNavbarVisible;
|
|
20
|
+
// Marcar que estamos en transición por 350ms (duración de la transición + margen)
|
|
21
|
+
isTransitioningRef.current = true;
|
|
22
|
+
const timer = setTimeout(() => {
|
|
23
|
+
isTransitioningRef.current = false;
|
|
24
|
+
}, 350);
|
|
25
|
+
return () => clearTimeout(timer);
|
|
18
26
|
}, [isNavbarVisible]);
|
|
19
|
-
// Controlar visibilidad del navbar basado en scroll
|
|
27
|
+
// Controlar visibilidad del navbar basado en scroll con histeresis mejorada
|
|
20
28
|
React.useEffect(() => {
|
|
29
|
+
// Ignorar cambios durante transiciones o cambios muy pequeños de scroll
|
|
30
|
+
if (isTransitioningRef.current) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const SCROLL_DELTA_THRESHOLD = 5; // Mínimo cambio de scroll para considerar
|
|
34
|
+
const scrollDelta = Math.abs(scrollY - lastScrollYRef.current);
|
|
35
|
+
// Ignorar cambios muy pequeños que pueden ser causados por el cambio de padding
|
|
36
|
+
if (scrollDelta < SCROLL_DELTA_THRESHOLD && lastScrollYRef.current > 0) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const SHOW_THRESHOLD = 80;
|
|
40
|
+
const HIDE_THRESHOLD = 120;
|
|
41
|
+
// Verificar si estamos cerca del final del scroll (margen de error de 10px)
|
|
42
|
+
const element = contentRef.current;
|
|
43
|
+
const isNearBottom = element
|
|
44
|
+
? Math.abs(element.scrollHeight - element.clientHeight - element.scrollTop) < 10
|
|
45
|
+
: false;
|
|
21
46
|
let shouldBeVisible;
|
|
22
|
-
if (scrollY <
|
|
47
|
+
if (scrollY < SHOW_THRESHOLD) {
|
|
23
48
|
// Siempre mostrar navbar cerca del top
|
|
24
49
|
shouldBeVisible = true;
|
|
25
50
|
}
|
|
26
|
-
else if (scrollDirection === "down" && scrollY >
|
|
27
|
-
// Ocultar navbar al hacer scroll hacia abajo
|
|
51
|
+
else if (scrollDirection === "down" && scrollY > HIDE_THRESHOLD && !isNearBottom) {
|
|
52
|
+
// Ocultar navbar al hacer scroll hacia abajo, excepto si estamos cerca del final
|
|
28
53
|
shouldBeVisible = false;
|
|
29
54
|
}
|
|
30
|
-
else if (scrollDirection === "up" && scrollY >
|
|
55
|
+
else if (scrollDirection === "up" && scrollY > SHOW_THRESHOLD) {
|
|
31
56
|
// Mostrar navbar al hacer scroll hacia arriba
|
|
32
57
|
shouldBeVisible = true;
|
|
33
58
|
}
|
|
59
|
+
else if (isNearBottom && scrollDirection === "down") {
|
|
60
|
+
// Si estamos en el final y scrolleamos hacia abajo, mantener el estado actual
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
34
63
|
else {
|
|
35
64
|
// No cambiar el estado si scrollDirection es null o no se cumple ninguna condición
|
|
36
65
|
return;
|
|
37
66
|
}
|
|
38
67
|
// Solo actualizar el estado si hay un cambio real
|
|
39
68
|
if (shouldBeVisible !== isNavbarVisibleRef.current) {
|
|
69
|
+
lastScrollYRef.current = scrollY;
|
|
40
70
|
setIsNavbarVisible(shouldBeVisible);
|
|
41
71
|
}
|
|
72
|
+
else {
|
|
73
|
+
// Actualizar la referencia del scroll incluso si no cambiamos la visibilidad
|
|
74
|
+
lastScrollYRef.current = scrollY;
|
|
75
|
+
}
|
|
42
76
|
}, [scrollDirection, scrollY]);
|
|
43
77
|
const handleMobileDrawerToggle = () => {
|
|
44
78
|
setIsMobileDrawerOpen(!isMobileDrawerOpen);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AutocompleteInputDocs.d.ts","sourceRoot":"","sources":["../../src/docs/AutocompleteInputDocs.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AA4E1B,QAAA,MAAM,qBAAqB,EAAE,KAAK,CAAC,EA4SlC,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
|