@axzydev/axzy_ui_system 1.2.0 → 1.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.
- package/dist/index.css +82 -197
- package/dist/index.css.map +1 -1
- package/package.json +2 -2
- package/src/App.tsx +354 -0
- package/src/assets/logo.png +0 -0
- package/src/assets/react.svg +1 -0
- package/src/components/alert/alert.props.ts +13 -0
- package/src/components/alert/alert.stories.tsx +41 -0
- package/src/components/alert/alert.tsx +53 -0
- package/src/components/avatar/avatar.props.ts +14 -0
- package/src/components/avatar/avatar.stories.tsx +46 -0
- package/src/components/avatar/avatar.tsx +53 -0
- package/src/components/badget/badget.props.ts +12 -0
- package/src/components/badget/badget.stories.tsx +76 -0
- package/src/components/badget/badget.tsx +61 -0
- package/src/components/breadcrumbs/breadcrumbs.props.ts +13 -0
- package/src/components/breadcrumbs/breadcrumbs.stories.tsx +21 -0
- package/src/components/breadcrumbs/breadcrumbs.tsx +34 -0
- package/src/components/button/button.props.ts +18 -0
- package/src/components/button/button.stories.tsx +174 -0
- package/src/components/button/button.tsx +117 -0
- package/src/components/calendar/calendar.props.ts +33 -0
- package/src/components/calendar/calendar.stories.tsx +91 -0
- package/src/components/calendar/calendar.tsx +608 -0
- package/src/components/calendar/index.ts +3 -0
- package/src/components/card/card.props.ts +13 -0
- package/src/components/card/card.stories.tsx +58 -0
- package/src/components/card/card.tsx +79 -0
- package/src/components/checkbox/checkbox.props.ts +11 -0
- package/src/components/checkbox/checkbox.stories.tsx +54 -0
- package/src/components/checkbox/checkbox.tsx +52 -0
- package/src/components/confirm-dialog/confirm-dialog.props.ts +14 -0
- package/src/components/confirm-dialog/confirm-dialog.stories.tsx +33 -0
- package/src/components/confirm-dialog/confirm-dialog.tsx +45 -0
- package/src/components/data-table/ITDataTable.stories.tsx +213 -0
- package/src/components/data-table/dataTable.props.ts +69 -0
- package/src/components/data-table/dataTable.tsx +313 -0
- package/src/components/date-picker/date-picker.props.ts +30 -0
- package/src/components/date-picker/date-picker.stories.tsx +90 -0
- package/src/components/date-picker/datePicker.tsx +307 -0
- package/src/components/dialog/dialog.props.ts +9 -0
- package/src/components/dialog/dialog.stories.tsx +80 -0
- package/src/components/dialog/dialog.tsx +88 -0
- package/src/components/divider/divider.props.ts +8 -0
- package/src/components/divider/divider.stories.tsx +34 -0
- package/src/components/divider/divider.tsx +21 -0
- package/src/components/drawer/drawer.props.ts +14 -0
- package/src/components/drawer/drawer.stories.tsx +41 -0
- package/src/components/drawer/drawer.tsx +53 -0
- package/src/components/dropfile/dropfile.stories.tsx +75 -0
- package/src/components/dropfile/dropfile.tsx +407 -0
- package/src/components/empty-state/empty-state.props.ts +9 -0
- package/src/components/empty-state/empty-state.stories.tsx +20 -0
- package/src/components/empty-state/empty-state.tsx +21 -0
- package/src/components/flex/flex.props.ts +22 -0
- package/src/components/flex/flex.stories.tsx +71 -0
- package/src/components/flex/flex.tsx +79 -0
- package/src/components/form-builder/fieldRenderer.tsx +218 -0
- package/src/components/form-builder/formBuilder.context.tsx +70 -0
- package/src/components/form-builder/formBuilder.props.ts +43 -0
- package/src/components/form-builder/formBuilder.stories.tsx +317 -0
- package/src/components/form-builder/formBuilder.tsx +186 -0
- package/src/components/form-builder/useFormBuilder.ts +80 -0
- package/src/components/form-header/form-header.props.ts +5 -0
- package/src/components/form-header/form-header.tsx +38 -0
- package/src/components/grid/grid.props.ts +17 -0
- package/src/components/grid/grid.stories.tsx +72 -0
- package/src/components/grid/grid.tsx +69 -0
- package/src/components/image/image.props.ts +7 -0
- package/src/components/image/image.tsx +38 -0
- package/src/components/input/input.props.ts +49 -0
- package/src/components/input/input.stories.tsx +115 -0
- package/src/components/input/input.tsx +615 -0
- package/src/components/layout/layout.props.ts +10 -0
- package/src/components/layout/layout.stories.tsx +114 -0
- package/src/components/layout/layout.tsx +80 -0
- package/src/components/loader/loader.props.ts +8 -0
- package/src/components/loader/loader.stories.tsx +105 -0
- package/src/components/loader/loader.tsx +108 -0
- package/src/components/navbar/navbar.props.ts +37 -0
- package/src/components/navbar/navbar.tsx +328 -0
- package/src/components/page/page.props.ts +19 -0
- package/src/components/page/page.stories.tsx +98 -0
- package/src/components/page/page.tsx +90 -0
- package/src/components/page-header/page-header.props.ts +11 -0
- package/src/components/page-header/page-header.stories.tsx +61 -0
- package/src/components/page-header/page-header.tsx +62 -0
- package/src/components/pagination/pagination.props.ts +53 -0
- package/src/components/pagination/pagination.stories.tsx +111 -0
- package/src/components/pagination/pagination.tsx +241 -0
- package/src/components/popover/popover.props.ts +12 -0
- package/src/components/popover/popover.stories.tsx +25 -0
- package/src/components/popover/popover.tsx +45 -0
- package/src/components/progress/progress.props.ts +12 -0
- package/src/components/progress/progress.stories.tsx +40 -0
- package/src/components/progress/progress.tsx +52 -0
- package/src/components/radio/radio.props.ts +16 -0
- package/src/components/radio/radio.stories.tsx +50 -0
- package/src/components/radio/radio.tsx +58 -0
- package/src/components/search-select/index.ts +2 -0
- package/src/components/search-select/search-select.props.ts +46 -0
- package/src/components/search-select/search-select.stories.tsx +129 -0
- package/src/components/search-select/search-select.tsx +229 -0
- package/src/components/searchTable/components/EditableCell.tsx +149 -0
- package/src/components/searchTable/components/PaginationControls.tsx +86 -0
- package/src/components/searchTable/components/PaginationInfo.tsx +20 -0
- package/src/components/searchTable/components/SearchAndSortBar.tsx +53 -0
- package/src/components/searchTable/components/SearchInput.tsx +33 -0
- package/src/components/searchTable/components/SortButton.tsx +50 -0
- package/src/components/searchTable/components/TableEmptyState.tsx +22 -0
- package/src/components/searchTable/components/TableHeader.tsx +35 -0
- package/src/components/searchTable/components/TableHeaderCell.tsx +43 -0
- package/src/components/searchTable/components/TableRow.tsx +144 -0
- package/src/components/searchTable/searchTable.props.ts +56 -0
- package/src/components/searchTable/searchTable.tsx +187 -0
- package/src/components/segmented-control/segmented-control.props.ts +18 -0
- package/src/components/segmented-control/segmented-control.stories.tsx +63 -0
- package/src/components/segmented-control/segmented-control.tsx +52 -0
- package/src/components/select/select.props.ts +25 -0
- package/src/components/select/select.stories.tsx +86 -0
- package/src/components/select/select.tsx +150 -0
- package/src/components/sidebar/sidebar.props.ts +28 -0
- package/src/components/sidebar/sidebar.stories.tsx +117 -0
- package/src/components/sidebar/sidebar.tsx +313 -0
- package/src/components/skeleton/skeleton.props.ts +12 -0
- package/src/components/skeleton/skeleton.stories.tsx +30 -0
- package/src/components/skeleton/skeleton.tsx +45 -0
- package/src/components/slide/slide.props.ts +45 -0
- package/src/components/slide/slide.stories.tsx +121 -0
- package/src/components/slide/slide.tsx +109 -0
- package/src/components/slider/slider.props.ts +10 -0
- package/src/components/slider/slider.stories.tsx +30 -0
- package/src/components/slider/slider.tsx +49 -0
- package/src/components/stack/stack.props.ts +19 -0
- package/src/components/stack/stack.stories.tsx +79 -0
- package/src/components/stack/stack.tsx +79 -0
- package/src/components/stat-card/stat-card.props.ts +13 -0
- package/src/components/stat-card/stat-card.stories.tsx +41 -0
- package/src/components/stat-card/stat-card.tsx +44 -0
- package/src/components/stepper/stepper.css +26 -0
- package/src/components/stepper/stepper.props.ts +29 -0
- package/src/components/stepper/stepper.stories.tsx +155 -0
- package/src/components/stepper/stepper.tsx +227 -0
- package/src/components/table/table.props.ts +43 -0
- package/src/components/table/table.stories.tsx +189 -0
- package/src/components/table/table.tsx +376 -0
- package/src/components/tabs/tabs.props.ts +18 -0
- package/src/components/tabs/tabs.stories.tsx +32 -0
- package/src/components/tabs/tabs.tsx +74 -0
- package/src/components/text/text.props.ts +9 -0
- package/src/components/text/text.tsx +20 -0
- package/src/components/textarea/textarea.props.ts +15 -0
- package/src/components/textarea/textarea.stories.tsx +27 -0
- package/src/components/textarea/textarea.tsx +55 -0
- package/src/components/theme-provider/themeProvider.props.ts +28 -0
- package/src/components/theme-provider/themeProvider.tsx +1854 -0
- package/src/components/time-picker/timePicker.props.ts +16 -0
- package/src/components/time-picker/timePicker.stories.tsx +131 -0
- package/src/components/time-picker/timePicker.tsx +317 -0
- package/src/components/toast/toast.css +32 -0
- package/src/components/toast/toast.props.ts +13 -0
- package/src/components/toast/toast.stories.tsx +138 -0
- package/src/components/toast/toast.tsx +87 -0
- package/src/components/tooltip/tooltip.props.ts +11 -0
- package/src/components/tooltip/tooltip.stories.tsx +20 -0
- package/src/components/tooltip/tooltip.tsx +55 -0
- package/src/components/topbar/topbar.props.ts +21 -0
- package/src/components/topbar/topbar.stories.tsx +80 -0
- package/src/components/topbar/topbar.tsx +205 -0
- package/src/components/triple-filter/tripleFilter.props.ts +15 -0
- package/src/components/triple-filter/tripleFilter.stories.tsx +32 -0
- package/src/components/triple-filter/tripleFilter.tsx +50 -0
- package/src/hooks/useClickOutside.ts +21 -0
- package/src/hooks/useDebouncedSearch.ts +55 -0
- package/src/hooks/useEditableRow.ts +157 -0
- package/src/hooks/useTableState.ts +122 -0
- package/src/index.css +168 -0
- package/src/index.ts +165 -0
- package/src/main.tsx +9 -0
- package/src/showcases/DataShowcases.tsx +260 -0
- package/src/showcases/FeedbackShowcases.tsx +268 -0
- package/src/showcases/FormShowcases.tsx +1159 -0
- package/src/showcases/HomeShowcase.tsx +324 -0
- package/src/showcases/LayoutPrimitivesShowcases.tsx +569 -0
- package/src/showcases/NavigationShowcases.tsx +193 -0
- package/src/showcases/PageShowcases.tsx +207 -0
- package/src/showcases/ShowcaseLayout.tsx +139 -0
- package/src/showcases/StructureShowcases.tsx +152 -0
- package/src/types/badget.types.ts +37 -0
- package/src/types/button.types.ts +16 -0
- package/src/types/colors.types.ts +3 -0
- package/src/types/field.types.ts +103 -0
- package/src/types/formik.types.ts +15 -0
- package/src/types/input.types.ts +14 -0
- package/src/types/loader.types.ts +9 -0
- package/src/types/sizes.types.ts +1 -0
- package/src/types/table.types.ts +15 -0
- package/src/types/toast.types.ts +8 -0
- package/src/types/yup.types.ts +11 -0
- package/src/utils/color.utils.ts +99 -0
- package/src/utils/styles.ts +120 -0
- package/src/utils/table.utils.ts +10 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { FaCalendarAlt } from "react-icons/fa";
|
|
4
|
+
import { isBefore } from "date-fns";
|
|
5
|
+
import ITCalendar from "../calendar/calendar";
|
|
6
|
+
import ITInput from "../input/input";
|
|
7
|
+
import { ITDatePickerProps } from "./date-picker.props";
|
|
8
|
+
import { theme } from "@/theme/theme";
|
|
9
|
+
|
|
10
|
+
export default function ITDatePicker({
|
|
11
|
+
name,
|
|
12
|
+
value,
|
|
13
|
+
onChange,
|
|
14
|
+
onBlur,
|
|
15
|
+
variant = "primary",
|
|
16
|
+
size = "medium",
|
|
17
|
+
className,
|
|
18
|
+
calendarClassName,
|
|
19
|
+
disabled = false,
|
|
20
|
+
label,
|
|
21
|
+
touched,
|
|
22
|
+
error,
|
|
23
|
+
required,
|
|
24
|
+
placeholder,
|
|
25
|
+
minDate,
|
|
26
|
+
maxDate,
|
|
27
|
+
range = false,
|
|
28
|
+
}: ITDatePickerProps) {
|
|
29
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
30
|
+
const [inputValue, setInputValue] = useState("");
|
|
31
|
+
const [isValidDate, setIsValidDate] = useState(true);
|
|
32
|
+
|
|
33
|
+
// For range selection, we'll keep track of the internal state if not provided
|
|
34
|
+
const [internalRange, setInternalRange] = useState<[Date | null, Date | null]>([null, null]);
|
|
35
|
+
|
|
36
|
+
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
37
|
+
const [calendarPosition, setCalendarPosition] = useState({ top: 0, left: 0 });
|
|
38
|
+
|
|
39
|
+
// Normalize single vs range values
|
|
40
|
+
const dateRange = React.useMemo(() => {
|
|
41
|
+
if (range) {
|
|
42
|
+
if (Array.isArray(value)) return value;
|
|
43
|
+
return internalRange;
|
|
44
|
+
}
|
|
45
|
+
return [value instanceof Date ? value : null, null] as [Date | null, Date | null];
|
|
46
|
+
}, [value, range, internalRange]);
|
|
47
|
+
|
|
48
|
+
const [startDate, endDate] = dateRange;
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (range) {
|
|
52
|
+
if (startDate && endDate) {
|
|
53
|
+
setInputValue(`${formatDate(startDate)} - ${formatDate(endDate)}`);
|
|
54
|
+
} else if (startDate) {
|
|
55
|
+
setInputValue(`${formatDate(startDate)} - ...`);
|
|
56
|
+
} else {
|
|
57
|
+
setInputValue("");
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
if (startDate instanceof Date && !isNaN(startDate.getTime())) {
|
|
61
|
+
setInputValue(formatDate(startDate));
|
|
62
|
+
} else {
|
|
63
|
+
setInputValue("");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}, [startDate, endDate, range]);
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
70
|
+
if (
|
|
71
|
+
wrapperRef.current &&
|
|
72
|
+
!wrapperRef.current.contains(event.target as Node)
|
|
73
|
+
) {
|
|
74
|
+
setIsOpen(false);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
78
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
79
|
+
}, []);
|
|
80
|
+
|
|
81
|
+
const calculateCalendarPosition = () => {
|
|
82
|
+
if (wrapperRef.current) {
|
|
83
|
+
const inputRect = wrapperRef.current.getBoundingClientRect();
|
|
84
|
+
const calendarHeight = 300;
|
|
85
|
+
const viewportHeight = window.innerHeight;
|
|
86
|
+
|
|
87
|
+
let top = inputRect.bottom + 4;
|
|
88
|
+
if (inputRect.bottom + calendarHeight > viewportHeight) {
|
|
89
|
+
top = inputRect.top - calendarHeight - 4;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
setCalendarPosition({
|
|
93
|
+
top,
|
|
94
|
+
left: inputRect.left,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const handleDateChange = (date: Date) => {
|
|
100
|
+
if (range) {
|
|
101
|
+
let newRange: [Date | null, Date | null];
|
|
102
|
+
|
|
103
|
+
if (!startDate || (startDate && endDate)) {
|
|
104
|
+
// Start a new range
|
|
105
|
+
newRange = [date, null];
|
|
106
|
+
} else {
|
|
107
|
+
// Closing a range
|
|
108
|
+
if (isBefore(date, startDate)) {
|
|
109
|
+
newRange = [date, startDate];
|
|
110
|
+
} else {
|
|
111
|
+
newRange = [startDate, date];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
setInternalRange(newRange);
|
|
116
|
+
|
|
117
|
+
// If range is complete, notify parent and close
|
|
118
|
+
if (newRange[0] && newRange[1]) {
|
|
119
|
+
onChange({
|
|
120
|
+
target: {
|
|
121
|
+
name,
|
|
122
|
+
value: newRange,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
setIsOpen(false);
|
|
126
|
+
} else {
|
|
127
|
+
// Just notify start (optional, but good for reactivity)
|
|
128
|
+
onChange({
|
|
129
|
+
target: {
|
|
130
|
+
name,
|
|
131
|
+
value: newRange,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
const event = {
|
|
137
|
+
target: {
|
|
138
|
+
name,
|
|
139
|
+
value: date,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
onChange(event);
|
|
143
|
+
setInputValue(formatDate(date));
|
|
144
|
+
setIsOpen(false);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const handleIconClick = () => {
|
|
149
|
+
if (!disabled) {
|
|
150
|
+
calculateCalendarPosition();
|
|
151
|
+
setIsOpen(!isOpen);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const formatDate = (date: Date) =>
|
|
156
|
+
date
|
|
157
|
+
.toLocaleDateString("es-ES", {
|
|
158
|
+
day: "2-digit",
|
|
159
|
+
month: "2-digit",
|
|
160
|
+
year: "numeric",
|
|
161
|
+
})
|
|
162
|
+
.replace(/\//g, "/");
|
|
163
|
+
|
|
164
|
+
const validateDate = (dateString: string) => {
|
|
165
|
+
const regex = /^(\d{2})\/(\d{2})\/(\d{4})$/;
|
|
166
|
+
const match = dateString.match(regex);
|
|
167
|
+
if (!match) return false;
|
|
168
|
+
|
|
169
|
+
const day = parseInt(match[1], 10);
|
|
170
|
+
const month = parseInt(match[2], 10);
|
|
171
|
+
const year = parseInt(match[3], 10);
|
|
172
|
+
|
|
173
|
+
const date = new Date(year, month - 1, day);
|
|
174
|
+
return (
|
|
175
|
+
date.getFullYear() === year &&
|
|
176
|
+
date.getMonth() === month - 1 &&
|
|
177
|
+
date.getDate() === day
|
|
178
|
+
);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
182
|
+
let val = e.target.value.replace(/\D/g, "");
|
|
183
|
+
if (val.length > 8) val = val.slice(0, 8);
|
|
184
|
+
|
|
185
|
+
if (val.length > 4) {
|
|
186
|
+
val = `${val.slice(0, 2)}/${val.slice(2, 4)}/${val.slice(4)}`;
|
|
187
|
+
} else if (val.length > 2) {
|
|
188
|
+
val = `${val.slice(0, 2)}/${val.slice(2)}`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
setInputValue(val);
|
|
192
|
+
|
|
193
|
+
if (val.length === 10 && validateDate(val)) {
|
|
194
|
+
const [day, month, year] = val.split("/").map(Number);
|
|
195
|
+
const date = new Date(year, month - 1, day);
|
|
196
|
+
const event = {
|
|
197
|
+
target: {
|
|
198
|
+
name,
|
|
199
|
+
value: date,
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
onChange(event);
|
|
203
|
+
onChange(event);
|
|
204
|
+
setIsValidDate(true);
|
|
205
|
+
} else {
|
|
206
|
+
setIsValidDate(false);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const handleInputBlur = () => {
|
|
211
|
+
if (range) {
|
|
212
|
+
// For range, simple text input is harder to validate without complex logic
|
|
213
|
+
// We'll rely on calendar for now to avoid breaking the UX
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!validateDate(inputValue)) {
|
|
218
|
+
// Si la fecha no es válida, usar la fecha de hoy
|
|
219
|
+
const today = new Date();
|
|
220
|
+
setInputValue(formatDate(today));
|
|
221
|
+
const event = {
|
|
222
|
+
target: {
|
|
223
|
+
name,
|
|
224
|
+
value: today,
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
onChange(event);
|
|
228
|
+
setIsValidDate(true);
|
|
229
|
+
} else {
|
|
230
|
+
// Solo construimos la fecha si es válida
|
|
231
|
+
const [day, month, year] = inputValue.split("/").map(Number);
|
|
232
|
+
const date = new Date(year, month - 1, day);
|
|
233
|
+
|
|
234
|
+
if (!isNaN(date.getTime())) {
|
|
235
|
+
onBlur?.({ target: { name, value: date } });
|
|
236
|
+
} else {
|
|
237
|
+
// fallback a hoy por seguridad
|
|
238
|
+
const today = new Date();
|
|
239
|
+
setInputValue(formatDate(today));
|
|
240
|
+
onChange({ target: { name, value: today } });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<div ref={wrapperRef} className={clsx("relative w-full", className)}>
|
|
247
|
+
<ITInput
|
|
248
|
+
name={name}
|
|
249
|
+
type="text"
|
|
250
|
+
label={label}
|
|
251
|
+
placeholder={placeholder}
|
|
252
|
+
value={inputValue}
|
|
253
|
+
onChange={handleInputChange}
|
|
254
|
+
onBlur={handleInputBlur}
|
|
255
|
+
maxLength={10}
|
|
256
|
+
iconRight={
|
|
257
|
+
<span>
|
|
258
|
+
<FaCalendarAlt
|
|
259
|
+
onClick={handleIconClick}
|
|
260
|
+
className="text-slate-900 cursor-pointer"
|
|
261
|
+
/>
|
|
262
|
+
</span>
|
|
263
|
+
}
|
|
264
|
+
variant={variant}
|
|
265
|
+
size={size}
|
|
266
|
+
disabled={disabled}
|
|
267
|
+
required={required}
|
|
268
|
+
touched={touched}
|
|
269
|
+
error={!isValidDate ? "Fecha inválida" : error}
|
|
270
|
+
onClick={handleIconClick}
|
|
271
|
+
/>
|
|
272
|
+
|
|
273
|
+
{isOpen && (
|
|
274
|
+
<div
|
|
275
|
+
className={clsx(
|
|
276
|
+
"fixed z-[9999]",
|
|
277
|
+
calendarClassName,
|
|
278
|
+
range ? "w-[320px]" : "w-[280px]"
|
|
279
|
+
)}
|
|
280
|
+
style={{
|
|
281
|
+
top: `${calendarPosition.top}px`,
|
|
282
|
+
left: `${calendarPosition.left}px`,
|
|
283
|
+
backgroundColor: theme.card.backgroundColor,
|
|
284
|
+
borderColor: theme.card.borderColor,
|
|
285
|
+
borderWidth: '1px',
|
|
286
|
+
borderStyle: 'solid',
|
|
287
|
+
borderRadius: theme.card.borderRadius,
|
|
288
|
+
boxShadow: theme.card.shadow,
|
|
289
|
+
padding: '0.5rem',
|
|
290
|
+
}}
|
|
291
|
+
>
|
|
292
|
+
<ITCalendar
|
|
293
|
+
value={!range ? (startDate as Date) : undefined}
|
|
294
|
+
startDate={startDate as Date}
|
|
295
|
+
endDate={endDate as Date}
|
|
296
|
+
selectionMode={range ? 'range' : 'single'}
|
|
297
|
+
onChange={handleDateChange}
|
|
298
|
+
minDate={minDate}
|
|
299
|
+
maxDate={maxDate}
|
|
300
|
+
variant={variant}
|
|
301
|
+
className="h-auto border-none shadow-none w-full"
|
|
302
|
+
/>
|
|
303
|
+
</div>
|
|
304
|
+
)}
|
|
305
|
+
</div>
|
|
306
|
+
);
|
|
307
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import ITDialog from './dialog';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import ITButton from '../button/button';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Components/Feedback/ITDialog',
|
|
8
|
+
component: ITDialog,
|
|
9
|
+
parameters: {
|
|
10
|
+
layout: 'centered',
|
|
11
|
+
},
|
|
12
|
+
tags: ['autodocs'],
|
|
13
|
+
argTypes: {
|
|
14
|
+
isOpen: { control: 'boolean' },
|
|
15
|
+
onClose: { action: 'closed' },
|
|
16
|
+
title: { control: 'text' },
|
|
17
|
+
useFormHeader: { control: 'boolean' },
|
|
18
|
+
},
|
|
19
|
+
} satisfies Meta<typeof ITDialog>;
|
|
20
|
+
|
|
21
|
+
export default meta;
|
|
22
|
+
type Story = StoryObj<typeof meta>;
|
|
23
|
+
|
|
24
|
+
// Wrapper to handle open/close state
|
|
25
|
+
const DialogWrapper = (args: any) => {
|
|
26
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div>
|
|
30
|
+
<ITButton onClick={() => setIsOpen(true)}>Open Dialog</ITButton>
|
|
31
|
+
<ITDialog {...args} isOpen={isOpen} onClose={() => setIsOpen(false)}>
|
|
32
|
+
{args.children}
|
|
33
|
+
</ITDialog>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const Default: any = {
|
|
39
|
+
render: (args) => <DialogWrapper {...args} />,
|
|
40
|
+
args: {
|
|
41
|
+
title: 'Basic Dialog',
|
|
42
|
+
children: <p className="text-gray-600">This is a simple dialog usage standard headers.</p>,
|
|
43
|
+
useFormHeader: false,
|
|
44
|
+
className: 'w-96',
|
|
45
|
+
} as any,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const WithFormHeader: any = {
|
|
49
|
+
render: (args) => <DialogWrapper {...args} />,
|
|
50
|
+
args: {
|
|
51
|
+
title: 'Form Header Dialog',
|
|
52
|
+
children: (
|
|
53
|
+
<div className="space-y-4">
|
|
54
|
+
<p className="text-gray-600">This dialog uses the standard form header styling.</p>
|
|
55
|
+
<div className="flex justify-end">
|
|
56
|
+
<ITButton variant="primary">Confirm</ITButton>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
),
|
|
60
|
+
useFormHeader: true,
|
|
61
|
+
className: 'w-[500px]',
|
|
62
|
+
} as any,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const LongContent: any = {
|
|
66
|
+
render: (args) => <DialogWrapper {...args} />,
|
|
67
|
+
args: {
|
|
68
|
+
title: 'Terms of Service',
|
|
69
|
+
children: (
|
|
70
|
+
<div className="h-64 overflow-y-auto text-sm text-gray-600">
|
|
71
|
+
<p className="mb-4">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
|
|
72
|
+
<p className="mb-4">Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
|
73
|
+
<p className="mb-4">Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.</p>
|
|
74
|
+
<p className="mb-4">Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.</p>
|
|
75
|
+
</div>
|
|
76
|
+
),
|
|
77
|
+
useFormHeader: true,
|
|
78
|
+
className: 'w-[600px]',
|
|
79
|
+
} as any,
|
|
80
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { createPortal } from "react-dom";
|
|
2
|
+
import { theme } from "@/theme/theme";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import { FaRegTimesCircle } from "react-icons/fa";
|
|
5
|
+
import { ITDialogProps } from "./dialog.props";
|
|
6
|
+
import useClickOutside from "@/hooks/useClickOutside";
|
|
7
|
+
import ITFormHeader from "../form-header/form-header";
|
|
8
|
+
import ITText from "@/components/text/text";
|
|
9
|
+
|
|
10
|
+
export default function ITDialog({
|
|
11
|
+
isOpen,
|
|
12
|
+
onClose,
|
|
13
|
+
children,
|
|
14
|
+
className,
|
|
15
|
+
title,
|
|
16
|
+
useFormHeader = false,
|
|
17
|
+
fullScreen = false,
|
|
18
|
+
}: ITDialogProps) {
|
|
19
|
+
const modalRef = useRef<HTMLDivElement>(null);
|
|
20
|
+
|
|
21
|
+
useClickOutside(modalRef, onClose);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
const handleEscape = (event: KeyboardEvent) => {
|
|
25
|
+
if (event.key === "Escape") {
|
|
26
|
+
onClose();
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
if (isOpen) {
|
|
31
|
+
document.addEventListener("keydown", handleEscape);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return () => {
|
|
35
|
+
document.removeEventListener("keydown", handleEscape);
|
|
36
|
+
};
|
|
37
|
+
}, [isOpen, onClose]);
|
|
38
|
+
|
|
39
|
+
if (!isOpen) return null;
|
|
40
|
+
if (typeof document === "undefined") return null;
|
|
41
|
+
|
|
42
|
+
const content = (
|
|
43
|
+
<div
|
|
44
|
+
className={`fixed inset-0 flex ${
|
|
45
|
+
fullScreen ? "items-stretch" : "items-center justify-center"
|
|
46
|
+
} bg-black/50 z-[9999]`}
|
|
47
|
+
>
|
|
48
|
+
<div
|
|
49
|
+
ref={modalRef}
|
|
50
|
+
className={`overflow-hidden relative ${
|
|
51
|
+
fullScreen
|
|
52
|
+
? "w-screen h-screen max-w-none rounded-none m-0 flex flex-col"
|
|
53
|
+
: `${className || ""} ${useFormHeader ? "p-0" : "p-6"}`
|
|
54
|
+
}`}
|
|
55
|
+
style={{
|
|
56
|
+
backgroundColor: theme.card.backgroundColor,
|
|
57
|
+
borderRadius: fullScreen ? "0" : theme.card.borderRadius,
|
|
58
|
+
boxShadow: fullScreen ? "none" : theme.card.shadow,
|
|
59
|
+
borderWidth: fullScreen ? "0" : theme.card.borderWidth,
|
|
60
|
+
borderColor: theme.card.borderColor,
|
|
61
|
+
borderStyle: 'solid',
|
|
62
|
+
}}
|
|
63
|
+
>
|
|
64
|
+
{useFormHeader && title ? (
|
|
65
|
+
<>
|
|
66
|
+
<ITFormHeader title={title} onClose={onClose} />
|
|
67
|
+
<div className={fullScreen ? "flex-1 overflow-auto p-6" : "p-6"}>
|
|
68
|
+
{children}
|
|
69
|
+
</div>
|
|
70
|
+
</>
|
|
71
|
+
) : (
|
|
72
|
+
<>
|
|
73
|
+
<button
|
|
74
|
+
className="absolute top-2 right-2 text-gray-600 hover:text-gray-900"
|
|
75
|
+
onClick={onClose}
|
|
76
|
+
>
|
|
77
|
+
<FaRegTimesCircle />
|
|
78
|
+
</button>
|
|
79
|
+
{title && <ITText as="h2" className="text-xl font-semibold mb-4">{title}</ITText>}
|
|
80
|
+
<div>{children}</div>
|
|
81
|
+
</>
|
|
82
|
+
)}
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return createPortal(content, document.body);
|
|
88
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import ITDivider from "./divider";
|
|
3
|
+
import ITStack from "../stack/stack";
|
|
4
|
+
import ITFlex from "../flex/flex";
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof ITDivider> = {
|
|
7
|
+
title: "Components/Layout/ITDivider",
|
|
8
|
+
component: ITDivider,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
type Story = StoryObj<typeof ITDivider>;
|
|
14
|
+
|
|
15
|
+
export const Horizontal: Story = {
|
|
16
|
+
args: { orientation: "horizontal" },
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const Vertical: Story = {
|
|
20
|
+
decorators: [(Story) => <ITFlex gap={3} className="h-12"><span>Left</span><Story /><span>Right</span></ITFlex>],
|
|
21
|
+
args: { orientation: "vertical" },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const InStack: Story = {
|
|
25
|
+
render: () => (
|
|
26
|
+
<ITStack spacing={3}>
|
|
27
|
+
<div className="p-3 bg-slate-50 rounded-lg text-sm">Sección 1</div>
|
|
28
|
+
<ITDivider />
|
|
29
|
+
<div className="p-3 bg-slate-50 rounded-lg text-sm">Sección 2</div>
|
|
30
|
+
<ITDivider />
|
|
31
|
+
<div className="p-3 bg-slate-50 rounded-lg text-sm">Sección 3</div>
|
|
32
|
+
</ITStack>
|
|
33
|
+
),
|
|
34
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import { ITDividerProps } from "./divider.props";
|
|
3
|
+
|
|
4
|
+
export default function ITDivider({
|
|
5
|
+
orientation = "horizontal",
|
|
6
|
+
className,
|
|
7
|
+
color = "bg-slate-200 dark:bg-slate-700",
|
|
8
|
+
thickness = orientation === "horizontal" ? "h-px" : "w-px",
|
|
9
|
+
}: ITDividerProps) {
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
className={clsx(
|
|
13
|
+
"flex-shrink-0",
|
|
14
|
+
orientation === "horizontal" ? "w-full" : "self-stretch",
|
|
15
|
+
thickness,
|
|
16
|
+
color,
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ReactNode, CSSProperties } from "react";
|
|
2
|
+
|
|
3
|
+
export type DrawerPosition = "left" | "right";
|
|
4
|
+
|
|
5
|
+
export interface ITDrawerProps {
|
|
6
|
+
isOpen: boolean;
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
position?: DrawerPosition;
|
|
9
|
+
size?: string;
|
|
10
|
+
title?: ReactNode;
|
|
11
|
+
children?: ReactNode;
|
|
12
|
+
className?: string;
|
|
13
|
+
style?: CSSProperties;
|
|
14
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import ITDrawer from "./drawer";
|
|
4
|
+
import ITButton from "../button/button";
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof ITDrawer> = {
|
|
7
|
+
title: "Components/Overlay/ITDrawer",
|
|
8
|
+
component: ITDrawer,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
type Story = StoryObj<typeof ITDrawer>;
|
|
14
|
+
|
|
15
|
+
export const Default: Story = {
|
|
16
|
+
render: () => {
|
|
17
|
+
const [open, setOpen] = useState(false);
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
<ITButton label="Abrir Drawer" onClick={() => setOpen(true)} />
|
|
21
|
+
<ITDrawer isOpen={open} onClose={() => setOpen(false)} title="Panel Lateral">
|
|
22
|
+
<p className="text-sm text-slate-600 dark:text-slate-300">Contenido del drawer.</p>
|
|
23
|
+
</ITDrawer>
|
|
24
|
+
</>
|
|
25
|
+
);
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const PositionLeft: Story = {
|
|
30
|
+
render: () => {
|
|
31
|
+
const [open, setOpen] = useState(false);
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
<ITButton label="Abrir Izquierda" onClick={() => setOpen(true)} />
|
|
35
|
+
<ITDrawer isOpen={open} onClose={() => setOpen(false)} position="left" title="Menú">
|
|
36
|
+
<p className="text-sm text-slate-600">Drawer desde la izquierda.</p>
|
|
37
|
+
</ITDrawer>
|
|
38
|
+
</>
|
|
39
|
+
);
|
|
40
|
+
},
|
|
41
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import { ITDrawerProps } from "./drawer.props";
|
|
3
|
+
import { FaTimes } from "react-icons/fa";
|
|
4
|
+
import useClickOutside from "@/hooks/useClickOutside";
|
|
5
|
+
import { useRef } from "react";
|
|
6
|
+
import ITText from "@/components/text/text";
|
|
7
|
+
|
|
8
|
+
export default function ITDrawer({
|
|
9
|
+
isOpen,
|
|
10
|
+
onClose,
|
|
11
|
+
position = "right",
|
|
12
|
+
size = "w-80",
|
|
13
|
+
title,
|
|
14
|
+
children,
|
|
15
|
+
className,
|
|
16
|
+
style,
|
|
17
|
+
}: ITDrawerProps) {
|
|
18
|
+
const panelRef = useRef<HTMLDivElement>(null);
|
|
19
|
+
useClickOutside(panelRef, onClose);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
{isOpen && (
|
|
24
|
+
<div className="fixed inset-0 z-[100] flex">
|
|
25
|
+
<div className="absolute inset-0 bg-black/40 backdrop-blur-sm transition-opacity" />
|
|
26
|
+
<div
|
|
27
|
+
ref={panelRef}
|
|
28
|
+
className={clsx(
|
|
29
|
+
"relative z-10 h-full bg-white dark:bg-slate-900 shadow-2xl flex flex-col transition-transform duration-300",
|
|
30
|
+
position === "right" ? "ml-auto" : "mr-auto",
|
|
31
|
+
size,
|
|
32
|
+
className
|
|
33
|
+
)}
|
|
34
|
+
style={style}
|
|
35
|
+
>
|
|
36
|
+
{title && (
|
|
37
|
+
<div className="flex items-center justify-between px-5 py-4 border-b border-slate-200 dark:border-slate-700">
|
|
38
|
+
<ITText as="h2" className="text-lg font-bold text-slate-800 dark:text-white">{title}</ITText>
|
|
39
|
+
<button
|
|
40
|
+
onClick={onClose}
|
|
41
|
+
className="w-8 h-8 flex items-center justify-center rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-400 hover:text-slate-600 transition-colors"
|
|
42
|
+
>
|
|
43
|
+
<FaTimes size={14} />
|
|
44
|
+
</button>
|
|
45
|
+
</div>
|
|
46
|
+
)}
|
|
47
|
+
<div className="flex-1 overflow-y-auto p-5">{children}</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
)}
|
|
51
|
+
</>
|
|
52
|
+
);
|
|
53
|
+
}
|