@axzydev/axzy_ui_system 1.2.1 → 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.
Files changed (202) hide show
  1. package/dist/index.css +82 -1
  2. package/dist/index.css.map +1 -1
  3. package/package.json +2 -2
  4. package/src/App.tsx +354 -0
  5. package/src/assets/logo.png +0 -0
  6. package/src/assets/react.svg +1 -0
  7. package/src/components/alert/alert.props.ts +13 -0
  8. package/src/components/alert/alert.stories.tsx +41 -0
  9. package/src/components/alert/alert.tsx +53 -0
  10. package/src/components/avatar/avatar.props.ts +14 -0
  11. package/src/components/avatar/avatar.stories.tsx +46 -0
  12. package/src/components/avatar/avatar.tsx +53 -0
  13. package/src/components/badget/badget.props.ts +12 -0
  14. package/src/components/badget/badget.stories.tsx +76 -0
  15. package/src/components/badget/badget.tsx +61 -0
  16. package/src/components/breadcrumbs/breadcrumbs.props.ts +13 -0
  17. package/src/components/breadcrumbs/breadcrumbs.stories.tsx +21 -0
  18. package/src/components/breadcrumbs/breadcrumbs.tsx +34 -0
  19. package/src/components/button/button.props.ts +18 -0
  20. package/src/components/button/button.stories.tsx +174 -0
  21. package/src/components/button/button.tsx +117 -0
  22. package/src/components/calendar/calendar.props.ts +33 -0
  23. package/src/components/calendar/calendar.stories.tsx +91 -0
  24. package/src/components/calendar/calendar.tsx +608 -0
  25. package/src/components/calendar/index.ts +3 -0
  26. package/src/components/card/card.props.ts +13 -0
  27. package/src/components/card/card.stories.tsx +58 -0
  28. package/src/components/card/card.tsx +79 -0
  29. package/src/components/checkbox/checkbox.props.ts +11 -0
  30. package/src/components/checkbox/checkbox.stories.tsx +54 -0
  31. package/src/components/checkbox/checkbox.tsx +52 -0
  32. package/src/components/confirm-dialog/confirm-dialog.props.ts +14 -0
  33. package/src/components/confirm-dialog/confirm-dialog.stories.tsx +33 -0
  34. package/src/components/confirm-dialog/confirm-dialog.tsx +45 -0
  35. package/src/components/data-table/ITDataTable.stories.tsx +213 -0
  36. package/src/components/data-table/dataTable.props.ts +69 -0
  37. package/src/components/data-table/dataTable.tsx +313 -0
  38. package/src/components/date-picker/date-picker.props.ts +30 -0
  39. package/src/components/date-picker/date-picker.stories.tsx +90 -0
  40. package/src/components/date-picker/datePicker.tsx +307 -0
  41. package/src/components/dialog/dialog.props.ts +9 -0
  42. package/src/components/dialog/dialog.stories.tsx +80 -0
  43. package/src/components/dialog/dialog.tsx +88 -0
  44. package/src/components/divider/divider.props.ts +8 -0
  45. package/src/components/divider/divider.stories.tsx +34 -0
  46. package/src/components/divider/divider.tsx +21 -0
  47. package/src/components/drawer/drawer.props.ts +14 -0
  48. package/src/components/drawer/drawer.stories.tsx +41 -0
  49. package/src/components/drawer/drawer.tsx +53 -0
  50. package/src/components/dropfile/dropfile.stories.tsx +75 -0
  51. package/src/components/dropfile/dropfile.tsx +407 -0
  52. package/src/components/empty-state/empty-state.props.ts +9 -0
  53. package/src/components/empty-state/empty-state.stories.tsx +20 -0
  54. package/src/components/empty-state/empty-state.tsx +21 -0
  55. package/src/components/flex/flex.props.ts +22 -0
  56. package/src/components/flex/flex.stories.tsx +71 -0
  57. package/src/components/flex/flex.tsx +79 -0
  58. package/src/components/form-builder/fieldRenderer.tsx +218 -0
  59. package/src/components/form-builder/formBuilder.context.tsx +70 -0
  60. package/src/components/form-builder/formBuilder.props.ts +43 -0
  61. package/src/components/form-builder/formBuilder.stories.tsx +317 -0
  62. package/src/components/form-builder/formBuilder.tsx +186 -0
  63. package/src/components/form-builder/useFormBuilder.ts +80 -0
  64. package/src/components/form-header/form-header.props.ts +5 -0
  65. package/src/components/form-header/form-header.tsx +38 -0
  66. package/src/components/grid/grid.props.ts +17 -0
  67. package/src/components/grid/grid.stories.tsx +72 -0
  68. package/src/components/grid/grid.tsx +69 -0
  69. package/src/components/image/image.props.ts +7 -0
  70. package/src/components/image/image.tsx +38 -0
  71. package/src/components/input/input.props.ts +49 -0
  72. package/src/components/input/input.stories.tsx +115 -0
  73. package/src/components/input/input.tsx +615 -0
  74. package/src/components/layout/layout.props.ts +10 -0
  75. package/src/components/layout/layout.stories.tsx +114 -0
  76. package/src/components/layout/layout.tsx +80 -0
  77. package/src/components/loader/loader.props.ts +8 -0
  78. package/src/components/loader/loader.stories.tsx +105 -0
  79. package/src/components/loader/loader.tsx +108 -0
  80. package/src/components/navbar/navbar.props.ts +37 -0
  81. package/src/components/navbar/navbar.tsx +328 -0
  82. package/src/components/page/page.props.ts +19 -0
  83. package/src/components/page/page.stories.tsx +98 -0
  84. package/src/components/page/page.tsx +90 -0
  85. package/src/components/page-header/page-header.props.ts +11 -0
  86. package/src/components/page-header/page-header.stories.tsx +61 -0
  87. package/src/components/page-header/page-header.tsx +62 -0
  88. package/src/components/pagination/pagination.props.ts +53 -0
  89. package/src/components/pagination/pagination.stories.tsx +111 -0
  90. package/src/components/pagination/pagination.tsx +241 -0
  91. package/src/components/popover/popover.props.ts +12 -0
  92. package/src/components/popover/popover.stories.tsx +25 -0
  93. package/src/components/popover/popover.tsx +45 -0
  94. package/src/components/progress/progress.props.ts +12 -0
  95. package/src/components/progress/progress.stories.tsx +40 -0
  96. package/src/components/progress/progress.tsx +52 -0
  97. package/src/components/radio/radio.props.ts +16 -0
  98. package/src/components/radio/radio.stories.tsx +50 -0
  99. package/src/components/radio/radio.tsx +58 -0
  100. package/src/components/search-select/index.ts +2 -0
  101. package/src/components/search-select/search-select.props.ts +46 -0
  102. package/src/components/search-select/search-select.stories.tsx +129 -0
  103. package/src/components/search-select/search-select.tsx +229 -0
  104. package/src/components/searchTable/components/EditableCell.tsx +149 -0
  105. package/src/components/searchTable/components/PaginationControls.tsx +86 -0
  106. package/src/components/searchTable/components/PaginationInfo.tsx +20 -0
  107. package/src/components/searchTable/components/SearchAndSortBar.tsx +53 -0
  108. package/src/components/searchTable/components/SearchInput.tsx +33 -0
  109. package/src/components/searchTable/components/SortButton.tsx +50 -0
  110. package/src/components/searchTable/components/TableEmptyState.tsx +22 -0
  111. package/src/components/searchTable/components/TableHeader.tsx +35 -0
  112. package/src/components/searchTable/components/TableHeaderCell.tsx +43 -0
  113. package/src/components/searchTable/components/TableRow.tsx +144 -0
  114. package/src/components/searchTable/searchTable.props.ts +56 -0
  115. package/src/components/searchTable/searchTable.tsx +187 -0
  116. package/src/components/segmented-control/segmented-control.props.ts +18 -0
  117. package/src/components/segmented-control/segmented-control.stories.tsx +63 -0
  118. package/src/components/segmented-control/segmented-control.tsx +52 -0
  119. package/src/components/select/select.props.ts +25 -0
  120. package/src/components/select/select.stories.tsx +86 -0
  121. package/src/components/select/select.tsx +150 -0
  122. package/src/components/sidebar/sidebar.props.ts +28 -0
  123. package/src/components/sidebar/sidebar.stories.tsx +117 -0
  124. package/src/components/sidebar/sidebar.tsx +313 -0
  125. package/src/components/skeleton/skeleton.props.ts +12 -0
  126. package/src/components/skeleton/skeleton.stories.tsx +30 -0
  127. package/src/components/skeleton/skeleton.tsx +45 -0
  128. package/src/components/slide/slide.props.ts +45 -0
  129. package/src/components/slide/slide.stories.tsx +121 -0
  130. package/src/components/slide/slide.tsx +109 -0
  131. package/src/components/slider/slider.props.ts +10 -0
  132. package/src/components/slider/slider.stories.tsx +30 -0
  133. package/src/components/slider/slider.tsx +49 -0
  134. package/src/components/stack/stack.props.ts +19 -0
  135. package/src/components/stack/stack.stories.tsx +79 -0
  136. package/src/components/stack/stack.tsx +79 -0
  137. package/src/components/stat-card/stat-card.props.ts +13 -0
  138. package/src/components/stat-card/stat-card.stories.tsx +41 -0
  139. package/src/components/stat-card/stat-card.tsx +44 -0
  140. package/src/components/stepper/stepper.css +26 -0
  141. package/src/components/stepper/stepper.props.ts +29 -0
  142. package/src/components/stepper/stepper.stories.tsx +155 -0
  143. package/src/components/stepper/stepper.tsx +227 -0
  144. package/src/components/table/table.props.ts +43 -0
  145. package/src/components/table/table.stories.tsx +189 -0
  146. package/src/components/table/table.tsx +376 -0
  147. package/src/components/tabs/tabs.props.ts +18 -0
  148. package/src/components/tabs/tabs.stories.tsx +32 -0
  149. package/src/components/tabs/tabs.tsx +74 -0
  150. package/src/components/text/text.props.ts +9 -0
  151. package/src/components/text/text.tsx +20 -0
  152. package/src/components/textarea/textarea.props.ts +15 -0
  153. package/src/components/textarea/textarea.stories.tsx +27 -0
  154. package/src/components/textarea/textarea.tsx +55 -0
  155. package/src/components/theme-provider/themeProvider.props.ts +28 -0
  156. package/src/components/theme-provider/themeProvider.tsx +1854 -0
  157. package/src/components/time-picker/timePicker.props.ts +16 -0
  158. package/src/components/time-picker/timePicker.stories.tsx +131 -0
  159. package/src/components/time-picker/timePicker.tsx +317 -0
  160. package/src/components/toast/toast.css +32 -0
  161. package/src/components/toast/toast.props.ts +13 -0
  162. package/src/components/toast/toast.stories.tsx +138 -0
  163. package/src/components/toast/toast.tsx +87 -0
  164. package/src/components/tooltip/tooltip.props.ts +11 -0
  165. package/src/components/tooltip/tooltip.stories.tsx +20 -0
  166. package/src/components/tooltip/tooltip.tsx +55 -0
  167. package/src/components/topbar/topbar.props.ts +21 -0
  168. package/src/components/topbar/topbar.stories.tsx +80 -0
  169. package/src/components/topbar/topbar.tsx +205 -0
  170. package/src/components/triple-filter/tripleFilter.props.ts +15 -0
  171. package/src/components/triple-filter/tripleFilter.stories.tsx +32 -0
  172. package/src/components/triple-filter/tripleFilter.tsx +50 -0
  173. package/src/hooks/useClickOutside.ts +21 -0
  174. package/src/hooks/useDebouncedSearch.ts +55 -0
  175. package/src/hooks/useEditableRow.ts +157 -0
  176. package/src/hooks/useTableState.ts +122 -0
  177. package/src/index.css +168 -0
  178. package/src/index.ts +165 -0
  179. package/src/main.tsx +9 -0
  180. package/src/showcases/DataShowcases.tsx +260 -0
  181. package/src/showcases/FeedbackShowcases.tsx +268 -0
  182. package/src/showcases/FormShowcases.tsx +1159 -0
  183. package/src/showcases/HomeShowcase.tsx +324 -0
  184. package/src/showcases/LayoutPrimitivesShowcases.tsx +569 -0
  185. package/src/showcases/NavigationShowcases.tsx +193 -0
  186. package/src/showcases/PageShowcases.tsx +207 -0
  187. package/src/showcases/ShowcaseLayout.tsx +139 -0
  188. package/src/showcases/StructureShowcases.tsx +152 -0
  189. package/src/types/badget.types.ts +37 -0
  190. package/src/types/button.types.ts +16 -0
  191. package/src/types/colors.types.ts +3 -0
  192. package/src/types/field.types.ts +103 -0
  193. package/src/types/formik.types.ts +15 -0
  194. package/src/types/input.types.ts +14 -0
  195. package/src/types/loader.types.ts +9 -0
  196. package/src/types/sizes.types.ts +1 -0
  197. package/src/types/table.types.ts +15 -0
  198. package/src/types/toast.types.ts +8 -0
  199. package/src/types/yup.types.ts +11 -0
  200. package/src/utils/color.utils.ts +99 -0
  201. package/src/utils/styles.ts +120 -0
  202. 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,9 @@
1
+ export interface ITDialogProps {
2
+ isOpen: boolean;
3
+ onClose: () => void;
4
+ children: React.ReactNode;
5
+ className?: string;
6
+ title?: string;
7
+ useFormHeader?: boolean;
8
+ fullScreen?: boolean;
9
+ }
@@ -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,8 @@
1
+ export type DividerOrientation = "horizontal" | "vertical";
2
+
3
+ export interface ITDividerProps {
4
+ orientation?: DividerOrientation;
5
+ className?: string;
6
+ color?: string;
7
+ thickness?: string;
8
+ }
@@ -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
+ }