@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,75 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import ITDropfile, { FileTypeEnum, UploadStatus } from './dropfile';
3
+ import { useState } from 'react';
4
+
5
+ const meta = {
6
+ title: 'Components/Actions/ITDropfile',
7
+ component: ITDropfile,
8
+ parameters: {
9
+ layout: 'centered',
10
+ },
11
+ tags: ['autodocs'],
12
+ argTypes: {
13
+ onFileSelect: { action: 'file selected' },
14
+ onCancel: { action: 'cancelled' },
15
+ onSubmit: { action: 'submitted' },
16
+ showStatusBadge: { control: 'boolean' },
17
+ uploadStatus: {
18
+ control: 'select',
19
+ options: Object.values(UploadStatus),
20
+ },
21
+ },
22
+ } satisfies Meta<typeof ITDropfile>;
23
+
24
+ export default meta;
25
+ type Story = StoryObj<typeof meta>;
26
+
27
+ // Wrapper for stateful file handling in story
28
+ const DropfileWrapper = (args: any) => {
29
+ return <ITDropfile {...args} className="w-[500px]" />;
30
+ }
31
+
32
+ export const Default: any = {
33
+ render: (args) => <DropfileWrapper {...args} />,
34
+ args: {
35
+ showStatusBadge: true,
36
+ containerClassName: 'w-[400px]',
37
+ },
38
+ };
39
+
40
+ export const WithImage: any = {
41
+ render: (args) => <DropfileWrapper {...args} />,
42
+ args: {
43
+ showStatusBadge: true,
44
+ initialPreviewUrl: 'https://images.unsplash.com/photo-1575936123452-b67c3203c357?ixlib=rb-4.0.3&auto=format&fit=crop&w=1000&q=80',
45
+ uploadStatus: UploadStatus.UPLOADED,
46
+ containerClassName: 'w-[400px]',
47
+ },
48
+ parameters: {
49
+ docs: {
50
+ description: {
51
+ story: 'Simulates a pre-loaded image (e.g., editing an existing entry).'
52
+ }
53
+ }
54
+ }
55
+ };
56
+
57
+ export const Uploading: any = {
58
+ render: (args) => <DropfileWrapper {...args} />,
59
+ args: {
60
+ showStatusBadge: true,
61
+ initialPreviewUrl: 'https://images.unsplash.com/photo-1575936123452-b67c3203c357?ixlib=rb-4.0.3&auto=format&fit=crop&w=1000&q=80',
62
+ uploadStatus: UploadStatus.UPLOADING,
63
+ containerClassName: 'w-[400px]',
64
+ },
65
+ };
66
+
67
+ export const ErrorState: any = {
68
+ render: (args) => <DropfileWrapper {...args} />,
69
+ args: {
70
+ showStatusBadge: true,
71
+ initialPreviewUrl: 'https://images.unsplash.com/photo-1575936123452-b67c3203c357?ixlib=rb-4.0.3&auto=format&fit=crop&w=1000&q=80',
72
+ uploadStatus: UploadStatus.ERROR,
73
+ containerClassName: 'w-[400px]',
74
+ },
75
+ };
@@ -0,0 +1,407 @@
1
+ import React, { useState, useEffect, useRef } from "react";
2
+ import { useDropzone } from "react-dropzone";
3
+ // import pdfjsLib from "@/hooks/pdf"; // Disabled as hook is missing
4
+ import clsx from "clsx";
5
+ import ITText from "@/components/text/text";
6
+
7
+ /** Enum con tipos de archivo permitidos */
8
+ export enum FileTypeEnum {
9
+ PDF = "application/pdf",
10
+ XLS = "application/vnd.ms-excel",
11
+ XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
12
+ CSV = "text/csv",
13
+ PNG = "image/png",
14
+ JPG = "image/jpg",
15
+ JPEG = "image/jpeg",
16
+ }
17
+
18
+ /** Enum para el estado de subida */
19
+ export enum UploadStatus {
20
+ PENDING = "pendiente",
21
+ UPLOADING = "subiendo",
22
+ UPLOADED = "subido",
23
+ ERROR = "error",
24
+ }
25
+
26
+ /** Props del componente */
27
+ export interface ITDropfileProps {
28
+ onFileSelect: (file: File | null) => void;
29
+ onCancel?: () => void;
30
+ onSubmit?: (file: File) => void;
31
+ acceptedFileTypes?: FileTypeEnum[];
32
+ contentClassName?: string;
33
+ containerClassName?: string;
34
+ showStatusBadge?: boolean;
35
+ uploadStatus?: UploadStatus;
36
+ onStatusChange?: (status: UploadStatus) => void;
37
+ initialPreviewUrl?: string | null;
38
+ }
39
+
40
+ const ITDropfile: React.FC<ITDropfileProps> = ({
41
+ onFileSelect,
42
+ onCancel,
43
+ onSubmit,
44
+ contentClassName,
45
+ containerClassName,
46
+ acceptedFileTypes = [FileTypeEnum.PDF, FileTypeEnum.XLS, FileTypeEnum.XLSX, FileTypeEnum.JPG, FileTypeEnum.PNG, FileTypeEnum.JPEG],
47
+ showStatusBadge = true,
48
+ uploadStatus: externalStatus,
49
+ onStatusChange,
50
+ initialPreviewUrl,
51
+ }) => {
52
+ const [selectedFile, setSelectedFile] = useState<File | null>(null);
53
+ const [fileType, setFileType] = useState<string | null>(null);
54
+ // Initialize preview with prop if available
55
+ const [imagePreview, setImagePreview] = useState<string | null>(initialPreviewUrl || null);
56
+ const [isConfirmed, setIsConfirmed] = useState(false);
57
+ // If initial URL exists, assume uploaded
58
+ const [internalUploadStatus, setInternalUploadStatus] = useState<UploadStatus>(
59
+ initialPreviewUrl ? UploadStatus.UPLOADED : UploadStatus.PENDING
60
+ );
61
+ const canvasRef = useRef<HTMLCanvasElement | null>(null);
62
+
63
+ // Sync initialPreviewUrl if it changes
64
+ useEffect(() => {
65
+ if (initialPreviewUrl && !selectedFile) {
66
+ setImagePreview(initialPreviewUrl);
67
+ if (externalStatus === undefined) setInternalUploadStatus(UploadStatus.UPLOADED);
68
+ }
69
+ }, [initialPreviewUrl, selectedFile, externalStatus]);
70
+
71
+ // Determinar qué estado usar (interno o externo)
72
+ const uploadStatus = externalStatus || internalUploadStatus;
73
+
74
+ // Función para actualizar el estado
75
+ const setUploadStatus = (status: UploadStatus) => {
76
+ if (externalStatus === undefined) {
77
+ setInternalUploadStatus(status);
78
+ }
79
+ onStatusChange?.(status);
80
+ };
81
+
82
+ // 🔹 Configuración correcta para react-dropzone
83
+ const getAcceptedFileTypes = () => {
84
+ const accept: Record<string, string[]> = {};
85
+
86
+ acceptedFileTypes.forEach(type => {
87
+ switch (type) {
88
+ case FileTypeEnum.PDF:
89
+ accept[FileTypeEnum.PDF] = [".pdf"];
90
+ break;
91
+ case FileTypeEnum.XLS:
92
+ accept[FileTypeEnum.XLS] = [".xls"];
93
+ break;
94
+ case FileTypeEnum.XLSX:
95
+ accept[FileTypeEnum.XLSX] = [".xlsx"];
96
+ break;
97
+ case FileTypeEnum.CSV:
98
+ accept[FileTypeEnum.CSV] = [".csv"];
99
+ break;
100
+ case FileTypeEnum.PNG:
101
+ accept[FileTypeEnum.PNG] = [".png"];
102
+ break;
103
+ case FileTypeEnum.JPG:
104
+ accept[FileTypeEnum.JPG] = [".jpg", ".jpeg"];
105
+ break;
106
+ case FileTypeEnum.JPEG:
107
+ accept[FileTypeEnum.JPEG] = [".jpeg", ".jpg"];
108
+ break;
109
+ }
110
+ });
111
+
112
+ return accept;
113
+ };
114
+
115
+ // 🔹 Obtener extensiones para mostrar en el label
116
+ const getFileExtensions = () => {
117
+ const extensions: string[] = [];
118
+
119
+ acceptedFileTypes.forEach(type => {
120
+ switch (type) {
121
+ case FileTypeEnum.PDF:
122
+ if (!extensions.includes("PDF")) extensions.push("PDF");
123
+ break;
124
+ case FileTypeEnum.XLS:
125
+ case FileTypeEnum.XLSX:
126
+ if (!extensions.includes("EXCEL")) extensions.push("EXCEL");
127
+ break;
128
+ case FileTypeEnum.CSV:
129
+ if (!extensions.includes("CSV")) extensions.push("CSV");
130
+ break;
131
+ case FileTypeEnum.PNG:
132
+ case FileTypeEnum.JPG:
133
+ case FileTypeEnum.JPEG:
134
+ if (!extensions.includes("IMAGEN")) extensions.push("IMAGEN");
135
+ break;
136
+ }
137
+ });
138
+
139
+ return extensions.join(", ");
140
+ };
141
+
142
+ // Componente para el badge de estado
143
+ const StatusBadge = ({ status }: { status: UploadStatus }) => {
144
+ const config = {
145
+ [UploadStatus.PENDING]: {
146
+ label: "Pendiente",
147
+ color: "bg-warning-100 text-warning-800 border-warning-200",
148
+ dotColor: "bg-warning-400",
149
+ },
150
+ [UploadStatus.UPLOADING]: {
151
+ label: "Subiendo...",
152
+ color: "bg-primary-100 text-primary-800 border-primary-200",
153
+ dotColor: "bg-primary-400 animate-pulse",
154
+ },
155
+ [UploadStatus.UPLOADED]: {
156
+ label: "Subido",
157
+ color: "bg-success-100 text-success-800 border-success-200",
158
+ dotColor: "bg-success-400",
159
+ },
160
+ [UploadStatus.ERROR]: {
161
+ label: "Error",
162
+ color: "bg-danger-100 text-danger-800 border-danger-200",
163
+ dotColor: "bg-danger-400",
164
+ },
165
+ };
166
+
167
+ const { label, color, dotColor } = config[status];
168
+
169
+ return (
170
+ <div className={`inline-flex items-center gap-2 px-2.5 py-1 rounded-full border ${color}`}>
171
+ <div className={`w-2 h-2 rounded-full ${dotColor}`}></div>
172
+ <ITText as="span" className="text-xs font-medium flex items-center gap-1.5">
173
+ {label}
174
+ </ITText>
175
+ </div>
176
+ );
177
+ };
178
+
179
+ const onDrop = (acceptedFiles: File[]) => {
180
+ const file = acceptedFiles[0];
181
+ if (file) {
182
+ if (acceptedFileTypes.includes(file.type as FileTypeEnum)) {
183
+ setSelectedFile(file);
184
+ setFileType(file.type);
185
+ setUploadStatus(UploadStatus.PENDING);
186
+
187
+ if (imagePreview) {
188
+ URL.revokeObjectURL(imagePreview);
189
+ setImagePreview(null);
190
+ }
191
+
192
+ if (file.type.startsWith('image/')) {
193
+ const previewUrl = URL.createObjectURL(file);
194
+ setImagePreview(previewUrl);
195
+ }
196
+ } else {
197
+ alert(`Tipo de archivo no permitido.`);
198
+ setSelectedFile(null);
199
+ setFileType(null);
200
+ setImagePreview(null);
201
+ setUploadStatus(UploadStatus.PENDING);
202
+ }
203
+ }
204
+ };
205
+
206
+ const { getRootProps, getInputProps, isDragActive } = useDropzone({
207
+ onDrop,
208
+ accept: getAcceptedFileTypes(),
209
+ maxFiles: 1,
210
+ });
211
+
212
+ // Renderizar PDF si aplica (COMENTADO)
213
+ useEffect(() => {
214
+ const renderPDF = async () => {
215
+ /*
216
+ if (selectedFile && fileType === FileTypeEnum.PDF) {
217
+ // Logic for PDF would go here
218
+ }
219
+ */
220
+ };
221
+ renderPDF();
222
+ }, [selectedFile, fileType]);
223
+
224
+ useEffect(() => {
225
+ return () => {
226
+ if (imagePreview) {
227
+ URL.revokeObjectURL(imagePreview);
228
+ }
229
+ };
230
+ }, [imagePreview]);
231
+
232
+ const handleConfirm = async () => {
233
+ if (selectedFile) {
234
+ setIsConfirmed(true);
235
+ setUploadStatus(UploadStatus.UPLOADING);
236
+
237
+ try {
238
+ await new Promise(resolve => setTimeout(resolve, 1500)); // Simulación
239
+ onFileSelect(selectedFile);
240
+ onSubmit?.(selectedFile);
241
+ setUploadStatus(UploadStatus.UPLOADED);
242
+ } catch (error) {
243
+ setUploadStatus(UploadStatus.ERROR);
244
+ console.error("Error al subir archivo:", error);
245
+ }
246
+ }
247
+ };
248
+
249
+ const handleCancel = () => {
250
+ setSelectedFile(null);
251
+ setFileType(null);
252
+ setIsConfirmed(false);
253
+ setUploadStatus(UploadStatus.PENDING);
254
+ if (imagePreview) {
255
+ URL.revokeObjectURL(imagePreview);
256
+ setImagePreview(null);
257
+ }
258
+ onFileSelect(null);
259
+ onCancel?.();
260
+ };
261
+
262
+ const handleDelete = () => {
263
+ handleCancel();
264
+ };
265
+
266
+ const isImage = fileType && fileType.startsWith('image/');
267
+
268
+ return (
269
+ <div className={clsx("w-full transition-all duration-300", containerClassName)}>
270
+ <div className="flex items-center justify-between mb-2">
271
+ <label className="block text-sm font-semibold text-gray-700">
272
+ <ITText as="span">Subir archivo </ITText><ITText as="span" className="text-gray-400 font-normal text-xs">({getFileExtensions()})</ITText>
273
+ </label>
274
+
275
+ {showStatusBadge && selectedFile && (
276
+ <StatusBadge status={uploadStatus} />
277
+ )}
278
+ </div>
279
+
280
+ {!selectedFile && !imagePreview ? (
281
+ <div
282
+ {...getRootProps()}
283
+ className={`
284
+ relative group flex flex-col items-center justify-center w-full p-6
285
+ border-2 border-dashed rounded-xl cursor-pointer transition-all duration-300 ease-in-out
286
+ ${
287
+ isDragActive
288
+ ? "border-primary-500 bg-primary-50 scale-[1.01]"
289
+ : "border-gray-300 bg-white hover:border-primary-400 hover:bg-gray-50"
290
+ }
291
+ `}
292
+ >
293
+ <input {...getInputProps()} />
294
+
295
+ <div className={`mb-3 p-3 rounded-full transition-colors duration-300 ${isDragActive ? 'bg-primary-100' : 'bg-gray-100 group-hover:bg-primary-50'}`}>
296
+ <svg
297
+ className={`w-6 h-6 transition-colors duration-300 ${isDragActive ? 'text-primary-600' : 'text-gray-400 group-hover:text-primary-500'}`}
298
+ fill="none" viewBox="0 0 24 24" stroke="currentColor"
299
+ >
300
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
301
+ </svg>
302
+ </div>
303
+
304
+ <div className="text-center space-y-1">
305
+ <ITText as="p" className={`text-sm font-medium transition-colors duration-300 ${isDragActive ? 'text-primary-700' : 'text-gray-700'}`}>
306
+ {isDragActive ? "¡Suelta aquí!" : "Haz clic o arrastra"}
307
+ </ITText>
308
+ </div>
309
+ </div>
310
+ ) : (
311
+ <div className="w-full bg-white border border-gray-200 rounded-xl shadow-sm overflow-hidden animate-fade-in">
312
+ <div className="flex items-center justify-between p-3 bg-gray-50 border-b border-gray-100">
313
+ <div className="flex items-center gap-3 overflow-hidden">
314
+ <div className="flex-shrink-0 w-8 h-8 rounded-lg bg-primary-100 flex items-center justify-center text-primary-600">
315
+ {(selectedFile && fileType?.startsWith('image/')) || (!selectedFile && imagePreview) ? (
316
+ <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
317
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
318
+ </svg>
319
+ ) : (
320
+ <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
321
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
322
+ </svg>
323
+ )}
324
+ </div>
325
+ <div className="min-w-0">
326
+ <ITText as="p" className="text-xs font-medium text-gray-900 truncate" title={selectedFile?.name || "Imagen cargada"}>
327
+ {selectedFile?.name || "Imagen cargada"}
328
+ </ITText>
329
+ <ITText as="p" className="text-[10px] text-gray-500">
330
+ {selectedFile ? (selectedFile.size / 1024 / 1024).toFixed(2) + " MB" : ""}
331
+ </ITText>
332
+ </div>
333
+ </div>
334
+ </div>
335
+
336
+ <div className={clsx("relative bg-gray-100 flex items-center justify-center", !contentClassName ? "max-h-[200px] min-h-[100px] overflow-auto" : contentClassName)}>
337
+ {((selectedFile && fileType?.startsWith('image/')) || (!selectedFile && imagePreview)) ? (
338
+ <img
339
+ src={imagePreview}
340
+ alt="Vista previa"
341
+ className="w-full h-full object-contain max-h-[200px]"
342
+ />
343
+ ) : (
344
+ <div className="py-8 flex flex-col items-center text-gray-400">
345
+ <svg className="w-10 h-10 mb-2 opacity-50" fill="none" viewBox="0 0 24 24" stroke="currentColor">
346
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
347
+ </svg>
348
+ <ITText as="span" className="text-xs">Sin vista previa</ITText>
349
+ </div>
350
+ )}
351
+ </div>
352
+
353
+ <div className="px-3 py-2 bg-white border-t border-gray-100 flex justify-end gap-2">
354
+ {!isConfirmed ? (
355
+ <>
356
+ <button
357
+ type="button"
358
+ onClick={handleCancel}
359
+ className="px-3 py-1.5 text-xs font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
360
+ >
361
+ <ITText as="span">Cancelar</ITText>
362
+ </button>
363
+ <button
364
+ type="button"
365
+ onClick={handleConfirm}
366
+ className="px-3 py-1.5 text-xs font-medium text-white bg-primary-600 rounded-lg hover:bg-primary-700 shadow-sm transition-colors flex items-center gap-1"
367
+ >
368
+ <ITText as="span">Confirmar</ITText>
369
+ <svg className="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
370
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
371
+ </svg>
372
+ </button>
373
+ </>
374
+ ) : (
375
+ <button
376
+ type="button"
377
+ onClick={handleDelete}
378
+ className="px-3 py-1.5 text-xs font-medium text-danger-600 bg-danger-50 border border-danger-100 rounded-lg hover:bg-danger-100 transition-colors flex items-center gap-1"
379
+ >
380
+ <svg className="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
381
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
382
+ </svg>
383
+ <ITText as="span">Eliminar</ITText>
384
+ </button>
385
+ )}
386
+ </div>
387
+
388
+ {uploadStatus === UploadStatus.UPLOADING && (
389
+ <div className="px-4 pb-2">
390
+ <div className="w-full bg-gray-200 rounded-full h-1.5">
391
+ <div
392
+ className="bg-primary-600 h-1.5 rounded-full transition-all duration-1000 ease-out"
393
+ style={{
394
+ width: '100%',
395
+ animation: 'pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite'
396
+ }}
397
+ ></div>
398
+ </div>
399
+ </div>
400
+ )}
401
+ </div>
402
+ )}
403
+ </div>
404
+ );
405
+ };
406
+
407
+ export default ITDropfile;
@@ -0,0 +1,9 @@
1
+ import { ReactNode } from "react";
2
+
3
+ export interface ITEmptyStateProps {
4
+ icon?: ReactNode;
5
+ title: string;
6
+ description?: string;
7
+ action?: ReactNode;
8
+ className?: string;
9
+ }
@@ -0,0 +1,20 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import ITEmptyState from "./empty-state";
3
+ import ITButton from "../button/button";
4
+
5
+ const meta: Meta<typeof ITEmptyState> = {
6
+ title: "Components/Data Display/ITEmptyState",
7
+ component: ITEmptyState,
8
+ tags: ["autodocs"],
9
+ };
10
+
11
+ export default meta;
12
+ type Story = StoryObj<typeof ITEmptyState>;
13
+
14
+ export const Default: Story = {
15
+ args: {
16
+ title: "Sin resultados",
17
+ description: "No se encontraron datos para los filtros seleccionados.",
18
+ action: <ITButton label="Limpiar filtros" variant="outlined" size="small" />,
19
+ },
20
+ };
@@ -0,0 +1,21 @@
1
+ import clsx from "clsx";
2
+ import { ITEmptyStateProps } from "./empty-state.props";
3
+ import { FaInbox } from "react-icons/fa";
4
+ import ITText from "@/components/text/text";
5
+
6
+ export default function ITEmptyState({
7
+ icon = <FaInbox size={40} />,
8
+ title,
9
+ description,
10
+ action,
11
+ className,
12
+ }: ITEmptyStateProps) {
13
+ return (
14
+ <div className={clsx("flex flex-col items-center justify-center py-16 px-6 text-center", className)}>
15
+ <div className="text-slate-300 dark:text-slate-600 mb-4">{icon}</div>
16
+ <ITText as="h3" className="text-lg font-bold text-slate-700 dark:text-slate-200 mb-1">{title}</ITText>
17
+ {description && <ITText as="p" className="text-sm text-slate-400 dark:text-slate-500 max-w-sm mb-4">{description}</ITText>}
18
+ {action && <div>{action}</div>}
19
+ </div>
20
+ );
21
+ }
@@ -0,0 +1,22 @@
1
+ import { ReactNode, CSSProperties, ElementType } from "react";
2
+
3
+ export type FlexDirection = "row" | "column" | "row-reverse" | "column-reverse";
4
+ export type FlexAlign = "start" | "end" | "center" | "stretch" | "baseline";
5
+ export type FlexJustify = "start" | "end" | "center" | "between" | "around" | "evenly";
6
+ export type FlexWrap = "nowrap" | "wrap" | "wrap-reverse";
7
+
8
+ export interface ITFlexProps {
9
+ children?: ReactNode;
10
+ direction?: FlexDirection;
11
+ align?: FlexAlign;
12
+ justify?: FlexJustify;
13
+ wrap?: FlexWrap;
14
+ gap?: number;
15
+ grow?: boolean | number;
16
+ shrink?: boolean | number;
17
+ basis?: string | number;
18
+ className?: string;
19
+ style?: CSSProperties;
20
+ as?: ElementType;
21
+ onClick?: (e: React.MouseEvent) => void;
22
+ }
@@ -0,0 +1,71 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import ITFlex from "./flex";
3
+
4
+ const meta: Meta<typeof ITFlex> = {
5
+ title: "Layout/ITFlex",
6
+ component: ITFlex,
7
+ tags: ["autodocs"],
8
+ argTypes: {
9
+ direction: {
10
+ control: "select",
11
+ options: ["row", "column", "row-reverse", "column-reverse"],
12
+ },
13
+ align: {
14
+ control: "select",
15
+ options: [undefined, "start", "end", "center", "stretch", "baseline"],
16
+ },
17
+ justify: {
18
+ control: "select",
19
+ options: [undefined, "start", "end", "center", "between", "around", "evenly"],
20
+ },
21
+ wrap: {
22
+ control: "select",
23
+ options: [undefined, "nowrap", "wrap", "wrap-reverse"],
24
+ },
25
+ gap: { control: { type: "range", min: 0, max: 16, step: 1 } },
26
+ grow: { control: "boolean" },
27
+ },
28
+ };
29
+
30
+ export default meta;
31
+ type Story = StoryObj<typeof ITFlex>;
32
+
33
+ const Box = ({ children, className = "" }: { children: string; className?: string }) => (
34
+ <div className={`bg-primary-100 text-primary-800 rounded-lg p-4 text-center font-medium ${className}`}>
35
+ {children}
36
+ </div>
37
+ );
38
+
39
+ export const Row: Story = {
40
+ args: { direction: "row", gap: 3 },
41
+ render: (args) => (
42
+ <ITFlex {...args}>
43
+ <Box>Flex 1</Box>
44
+ <Box>Flex 2</Box>
45
+ <Box>Flex 3</Box>
46
+ </ITFlex>
47
+ ),
48
+ };
49
+
50
+ export const SpaceBetween: Story = {
51
+ args: { justify: "between", align: "center", className: "w-full" },
52
+ render: (args) => (
53
+ <ITFlex {...args}>
54
+ <Box>Left</Box>
55
+ <Box>Center</Box>
56
+ <Box>Right</Box>
57
+ </ITFlex>
58
+ ),
59
+ };
60
+
61
+ export const ColumnGrow: Story = {
62
+ args: { direction: "column", gap: 2, className: "h-60" },
63
+ render: (args) => (
64
+ <ITFlex {...args}>
65
+ <ITFlex grow className="bg-primary-100 rounded-lg p-4">
66
+ <Box>Grow (flex: 1)</Box>
67
+ </ITFlex>
68
+ <Box className="w-full">Fixed height</Box>
69
+ </ITFlex>
70
+ ),
71
+ };