@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,376 @@
1
+ import { useTableState } from "@/hooks/useTableState";
2
+ import { sizeStyles, variantStyles } from "@/types/table.types";
3
+ import clsx from "clsx";
4
+ import React from "react";
5
+ import {
6
+ FaCheck,
7
+ FaSpinner,
8
+ FaTimes
9
+ } from "react-icons/fa";
10
+ import { MdOutlineSwapVert } from "react-icons/md";
11
+ import ITInput from "../input/input";
12
+ import ITPagination from "../pagination/pagination";
13
+ import ITSelect from "../select/select";
14
+ import { Column, ITTableProps } from "./table.props";
15
+ import ITText from "@/components/text/text";
16
+
17
+ const getNestedValue = (obj: unknown, path: string) => {
18
+ return path.split(".").reduce((acc, part) => acc && acc[part], obj);
19
+ };
20
+ export const formatCurrencyMX = (value: number) => {
21
+ return value.toLocaleString("es-MX", {
22
+ style: "currency",
23
+ currency: "MXN",
24
+ });
25
+ };
26
+ export default function ITTable<T extends Record<string, unknown>>({
27
+ columns,
28
+ data = [],
29
+ containerClassName,
30
+ className,
31
+ variant = "default",
32
+ size = "md",
33
+ itemsPerPageOptions = [5, 10, 20],
34
+ defaultItemsPerPage = 10,
35
+ title,
36
+ }: ITTableProps<T>) {
37
+ const {
38
+ currentPage,
39
+ itemsPerPage,
40
+ filters,
41
+ sortConfig,
42
+ goToPage,
43
+ handleFilterChange,
44
+ handleSort,
45
+ handleItemsPerPageChange,
46
+ } = useTableState({ defaultItemsPerPage });
47
+
48
+ const sortedData = React.useMemo(() => {
49
+ const safeData = Array.isArray(data) ? data : [];
50
+ if (!sortConfig) return safeData;
51
+
52
+ return [...safeData].sort((a, b) => {
53
+ const aValue = getNestedValue(a, sortConfig.key);
54
+ const bValue = getNestedValue(b, sortConfig.key);
55
+
56
+ if (aValue == null || bValue == null) return 0;
57
+
58
+ let comparison = 0;
59
+
60
+ const column = columns.find((col) => col.key === sortConfig.key);
61
+ if (!column || !column.sortable) return 0;
62
+
63
+ switch (column.type) {
64
+ case "number":
65
+ comparison = (aValue as number) - (bValue as number);
66
+ break;
67
+ case "date":
68
+ comparison =
69
+ new Date(aValue as string).getTime() -
70
+ new Date(bValue as string).getTime();
71
+ break;
72
+ case "boolean":
73
+ comparison = aValue === bValue ? 0 : aValue ? 1 : -1;
74
+ break;
75
+ case "catalog": {
76
+ const catalogItemA = column.catalogOptions?.data.find(
77
+ (item) => item.id === aValue
78
+ );
79
+ const catalogItemB = column.catalogOptions?.data.find(
80
+ (item) => item.id === bValue
81
+ );
82
+ comparison = String(catalogItemA?.name || aValue).localeCompare(
83
+ String(catalogItemB?.name || bValue)
84
+ );
85
+ break;
86
+ }
87
+ case "string":
88
+ default:
89
+ comparison = (aValue as string).localeCompare(bValue as string);
90
+ break;
91
+ }
92
+
93
+ return sortConfig.direction === "asc" ? comparison : -comparison;
94
+ });
95
+ }, [data, sortConfig, columns]);
96
+
97
+ const filteredData = sortedData.filter((row) =>
98
+ columns.every((col) => {
99
+ if (
100
+ !col.filter ||
101
+ filters[col.key] === undefined ||
102
+ filters[col.key] === ""
103
+ )
104
+ return true;
105
+
106
+ const value = getNestedValue(row, col.key);
107
+ const filterValue = String(filters[col.key]).toLowerCase();
108
+
109
+ switch (col.type) {
110
+ case "number":
111
+ return String(value).includes(filterValue);
112
+ case "boolean":
113
+ return value === filters[col.key];
114
+ case "catalog": {
115
+ if (!col.catalogOptions) return true;
116
+ const catalogItem = col.catalogOptions.data.find(
117
+ (item) =>
118
+ String(item.id).toLowerCase().includes(filterValue) ||
119
+ item.name.toLowerCase().includes(filterValue)
120
+ );
121
+ return catalogItem ? value === catalogItem.id : false;
122
+ }
123
+ case "string":
124
+ default:
125
+ return String(value).toLowerCase().includes(filterValue);
126
+ }
127
+ })
128
+ );
129
+
130
+ const computedTotalPages = Math.ceil(filteredData.length / itemsPerPage) || 1;
131
+ const currentData = filteredData.slice(
132
+ (currentPage - 1) * itemsPerPage,
133
+ currentPage * itemsPerPage
134
+ );
135
+
136
+ const renderFilterInput = (col: Column<T>) => {
137
+ if (!col.filter) return null;
138
+
139
+ if (col.type === "boolean") {
140
+ const currentValue = filters[col.key];
141
+ const nextValue =
142
+ currentValue === undefined
143
+ ? true
144
+ : currentValue === true
145
+ ? false
146
+ : undefined;
147
+
148
+ const getToggleLabel = () => {
149
+ if (currentValue === undefined) return "Mostrar todos";
150
+ if (currentValue === true) return "Filtrar solo verdaderos";
151
+ return "Filtrar solo falsos";
152
+ };
153
+
154
+ return (
155
+ <button
156
+ className="flex items-center justify-center cursor-pointer focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 rounded-full p-1 transition-all duration-200"
157
+ onClick={() => handleFilterChange(col.key, nextValue)}
158
+ aria-label={`${getToggleLabel()} para ${col.label}`}
159
+ title={`${getToggleLabel()} para ${col.label}`}
160
+ >
161
+ <div className="relative w-10 h-5 bg-gray-300 rounded-full">
162
+ <div
163
+ className={clsx(
164
+ "absolute top-0.5 w-4 h-4 rounded-full transition-all duration-300 shadow-sm",
165
+ {
166
+ "left-0.5 bg-gray-400": currentValue === undefined,
167
+ "left-5 bg-slate-500": currentValue === true,
168
+ "left-0.5 bg-gray-500": currentValue === false,
169
+ }
170
+ )}
171
+ />
172
+ </div>
173
+ </button>
174
+ );
175
+ }
176
+
177
+ if (col.filter === "catalog" && col.catalogOptions) {
178
+ if (col.catalogOptions.loading) {
179
+ return (
180
+ <FaSpinner
181
+ className="animate-spin"
182
+ aria-label="Cargando opciones"
183
+ title="Cargando opciones"
184
+ />
185
+ );
186
+ }
187
+
188
+ if (col.catalogOptions.error) {
189
+ return <ITText as="span" className="text-red-500 text-xs">Error cargando</ITText>;
190
+ }
191
+
192
+ return (
193
+ <ITSelect
194
+ name={`filter-${col.key}`}
195
+ options={[
196
+ { value: "", label: "Todos" },
197
+ ...col.catalogOptions.data.map((item) => ({
198
+ value: String(item.id),
199
+ label: item.name,
200
+ })),
201
+ ]}
202
+ value={String(filters[col.key] || "")}
203
+ onChange={(e) => {
204
+ const value = e.target.value === "" ? undefined : e.target.value;
205
+ handleFilterChange(col.key, value);
206
+ }}
207
+ onBlur={() => {}}
208
+ className="w-full text-xs"
209
+ />
210
+ );
211
+ }
212
+
213
+ return (
214
+ <ITInput
215
+ name={`filter-${col.key}`}
216
+ className="w-full text-xs"
217
+ placeholder="Buscar..."
218
+ value={String(filters[col.key] || "")}
219
+ onChange={(e) => handleFilterChange(col.key, e.target.value)}
220
+ onBlur={() => {}}
221
+ />
222
+ );
223
+ };
224
+
225
+ const renderCellContent = (col: Column<T>, row: T) => {
226
+ const value = getNestedValue(row, col.key);
227
+
228
+ if (col.render) {
229
+ return col.render(row);
230
+ }
231
+
232
+ switch (col.type) {
233
+ case "number":
234
+ return (typeof value === "number") && col.currencyMX ?formatCurrencyMX(value) : value;
235
+ case "boolean":
236
+ return value ? (
237
+ <FaCheck
238
+ className="text-green-500"
239
+ aria-label="Verdadero"
240
+ title="Verdadero"
241
+ />
242
+ ) : (
243
+ <FaTimes
244
+ className="text-red-500"
245
+ aria-label="Falso"
246
+ title="Falso"
247
+ />
248
+ );
249
+ case "actions":
250
+ return col.actions ? col.actions(row) : null;
251
+ case "catalog":
252
+ if (col.catalogOptions) {
253
+ const catalogItem = col.catalogOptions.data.find(
254
+ (item) => item.id === value
255
+ );
256
+ return catalogItem?.name || value;
257
+ }
258
+ return value as React.ReactNode;
259
+ default:
260
+ return value as React.ReactNode;
261
+ }
262
+ };
263
+
264
+ return (
265
+ <div className={clsx("space-y-4 w-full", containerClassName)}>
266
+ <div className="rounded-xl shadow-sm border border-secondary-200 overflow-hidden" style={{ backgroundColor: 'var(--color-table-rowBg, #ffffff)' }}>
267
+ {/* Header outside overflow */}
268
+ {title && (
269
+ <div className="px-6 py-5 border-b border-secondary-100" style={{ backgroundColor: 'var(--color-table-rowBg, #ffffff)' }}>
270
+ <ITText as="h2" className="text-xl font-bold text-secondary-900 leading-tight">{title}</ITText>
271
+ </div>
272
+ )}
273
+
274
+ {/* Scrollable Table */}
275
+ <div className="overflow-x-auto">
276
+ <table
277
+ className={clsx(
278
+ "min-w-max w-full text-sm text-left text-secondary-600",
279
+ variantStyles[variant],
280
+ sizeStyles[size]
281
+ )}
282
+ >
283
+
284
+ <thead>
285
+ <tr className="bg-secondary-50 border-b border-secondary-200 text-xs uppercase tracking-wider font-semibold text-secondary-500">
286
+ {columns.map((col) => (
287
+ <th
288
+ key={col.key}
289
+ scope="col"
290
+ className={clsx("px-4 py-4 align-top", col.className)}
291
+ >
292
+ <div className="flex flex-col gap-3 min-w-[150px]">
293
+ {/* Column header */}
294
+ <div className="flex items-center justify-between gap-2">
295
+ <ITText as="span" className="text-secondary-700 font-bold">{col.label}</ITText>
296
+ {col.sortable && col.type !== "actions" && (
297
+ <button
298
+ onClick={() => handleSort(col.key)}
299
+ className={`p-1 rounded-md transition-colors ${
300
+ sortConfig?.key === col.key
301
+ ? "bg-secondary-200 text-secondary-900"
302
+ : "hover:bg-secondary-200 text-secondary-400 hover:text-secondary-700"
303
+ }`}
304
+ title={`Ordenar por ${col.label}`}
305
+ >
306
+ <MdOutlineSwapVert className="w-4 h-4" aria-hidden="true" />
307
+ </button>
308
+ )}
309
+ </div>
310
+
311
+ {/* Filter section */}
312
+ <div className="w-full">
313
+ {col.filter ? renderFilterInput(col) : null}
314
+ </div>
315
+ </div>
316
+ </th>
317
+ ))}
318
+ </tr>
319
+ </thead>
320
+ <tbody className="divide-y divide-secondary-100">
321
+ {currentData.length > 0 ? (
322
+ currentData.map((row, rowIndex) => (
323
+ <tr
324
+ key={rowIndex}
325
+ className="hover:bg-secondary-50/50 transition-colors duration-150 group"
326
+ >
327
+ {columns.map((col) => (
328
+ <td
329
+ key={`${rowIndex}-${col.key}`}
330
+ className={clsx("px-4 py-3 align-middle", col.className)}
331
+ >
332
+ {col.type === "actions" ? (
333
+ <div className="flex items-center justify-center gap-2">
334
+ {renderCellContent(col, row) as React.ReactNode}
335
+ </div>
336
+ ) : (
337
+ <div className="text-secondary-700 font-medium">
338
+ {renderCellContent(col, row) as React.ReactNode}
339
+ </div>
340
+ )}
341
+ </td>
342
+ ))}
343
+ </tr>
344
+ ))
345
+ ) : (
346
+ <tr>
347
+ <td colSpan={columns.length} className="px-6 py-12 text-center">
348
+ <div className="flex flex-col items-center justify-center text-secondary-400">
349
+ <ITText as="span" className="text-lg">No se encontraron resultados</ITText>
350
+ <ITText as="span" className="text-sm mt-1">Intenta ajustar los filtros</ITText>
351
+ </div>
352
+ </td>
353
+ </tr>
354
+ )}
355
+ </tbody>
356
+ </table>
357
+ </div>
358
+
359
+
360
+ {/* Pagination */}
361
+ <div className="rounded-b-xl border-t border-secondary-200 px-6 py-4" style={{ backgroundColor: 'var(--color-table-rowBg, #ffffff)' }}>
362
+ <ITPagination
363
+ currentPage={currentPage}
364
+ totalPages={computedTotalPages}
365
+ onPageChange={goToPage}
366
+ color="primary"
367
+ itemsPerPageOptions={itemsPerPageOptions}
368
+ itemsPerPage={itemsPerPage}
369
+ onItemsPerPageChange={handleItemsPerPageChange}
370
+ totalItems={filteredData.length}
371
+ />
372
+ </div>
373
+ </div>
374
+ </div>
375
+ );
376
+ }
@@ -0,0 +1,18 @@
1
+ import { ReactNode } from "react";
2
+
3
+ export interface ITTabItem {
4
+ id: string;
5
+ label: string;
6
+ content: ReactNode;
7
+ icon?: ReactNode;
8
+ disabled?: boolean;
9
+ }
10
+
11
+ export interface ITTabsProps {
12
+ items: ITTabItem[];
13
+ defaultActiveId?: string;
14
+ onChange?: (id: string) => void;
15
+ variant?: 'line' | 'pill';
16
+ className?: string;
17
+ containerClassName?: string;
18
+ }
@@ -0,0 +1,32 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import ITTabs from './tabs';
3
+
4
+ const meta: Meta<typeof ITTabs> = {
5
+ title: 'Components/ITTabs',
6
+ component: ITTabs,
7
+ tags: ['autodocs'],
8
+ };
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof ITTabs>;
12
+
13
+ export const Line: Story = {
14
+ args: {
15
+ variant: 'line',
16
+ items: [
17
+ { id: '1', label: 'Tab 1', content: <div className="p-4">Content 1</div> },
18
+ { id: '2', label: 'Tab 2', content: <div className="p-4">Content 2</div> },
19
+ { id: '3', label: 'Disabled', content: <div>X</div>, disabled: true },
20
+ ],
21
+ },
22
+ };
23
+
24
+ export const Pill: Story = {
25
+ args: {
26
+ variant: 'pill',
27
+ items: [
28
+ { id: '1', label: 'Overview', content: <div className="p-4">Dashboard Overview</div> },
29
+ { id: '2', label: 'Settings', content: <div className="p-4">Account Settings</div> },
30
+ ],
31
+ },
32
+ };
@@ -0,0 +1,74 @@
1
+ import React, { useState } from 'react';
2
+ import { ITTabsProps } from './tabs.props';
3
+ import { clsx } from 'clsx';
4
+ import ITText from "@/components/text/text";
5
+
6
+ const ITTabs: React.FC<ITTabsProps> = ({
7
+ items,
8
+ defaultActiveId,
9
+ onChange,
10
+ variant = 'line',
11
+ className = '',
12
+ containerClassName = ''
13
+ }) => {
14
+ const [activeId, setActiveId] = useState(defaultActiveId || items[0]?.id);
15
+
16
+ const handleTabClick = (id: string, disabled?: boolean) => {
17
+ if (disabled) return;
18
+ setActiveId(id);
19
+ if (onChange) onChange(id);
20
+ };
21
+
22
+ const activeContent = items.find(item => item.id === activeId)?.content;
23
+
24
+ return (
25
+ <div className={clsx("w-full", containerClassName)}>
26
+ {/* HEADER */}
27
+ <div className={clsx(
28
+ "flex border-gray-200 mb-4",
29
+ variant === 'line' ? "border-b" : "gap-2 p-1 bg-gray-100 rounded-lg w-fit",
30
+ className
31
+ )}>
32
+ {items.map((item) => {
33
+ const isActive = item.id === activeId;
34
+
35
+ return (
36
+ <button
37
+ key={item.id}
38
+ onClick={() => handleTabClick(item.id, item.disabled)}
39
+ disabled={item.disabled}
40
+ className={clsx(
41
+ "flex items-center gap-2 px-4 py-2 text-sm font-medium transition-all duration-200 outline-none",
42
+ // LINE VARIANT
43
+ variant === 'line' && [
44
+ "border-b-2 -mb-[2px]",
45
+ isActive
46
+ ? "border-primary-500 text-primary-600"
47
+ : "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300",
48
+ ],
49
+ // PILL VARIANT
50
+ variant === 'pill' && [
51
+ "rounded-md",
52
+ isActive
53
+ ? "bg-white text-primary-600 shadow-sm"
54
+ : "text-gray-500 hover:text-gray-700",
55
+ ],
56
+ item.disabled && "opacity-50 cursor-not-allowed"
57
+ )}
58
+ >
59
+ {item.icon && <span className="w-4 h-4">{item.icon}</span>}
60
+ <ITText as="span">{item.label}</ITText>
61
+ </button>
62
+ );
63
+ })}
64
+ </div>
65
+
66
+ {/* CONTENT */}
67
+ <div className="tab-content animate-fadeIn">
68
+ {activeContent}
69
+ </div>
70
+ </div>
71
+ );
72
+ };
73
+
74
+ export default ITTabs;
@@ -0,0 +1,9 @@
1
+ import { ReactNode, ElementType, HTMLAttributes } from "react";
2
+
3
+ export interface ITTextProps extends HTMLAttributes<HTMLElement> {
4
+ children?: ReactNode;
5
+ className?: string;
6
+ as?: ElementType;
7
+ muted?: boolean;
8
+ htmlFor?: string;
9
+ }
@@ -0,0 +1,20 @@
1
+ import { ITTextProps } from "./text.props";
2
+
3
+ export default function ITText({
4
+ children,
5
+ as: Tag = "p",
6
+ className = "",
7
+ muted = false,
8
+ style,
9
+ ...rest
10
+ }: ITTextProps & { style?: React.CSSProperties }) {
11
+ return (
12
+ <Tag
13
+ className={className}
14
+ style={muted ? { color: "var(--color-text-muted)", ...style } : { color: "var(--color-text-default)", ...style }}
15
+ {...rest}
16
+ >
17
+ {children}
18
+ </Tag>
19
+ );
20
+ }
@@ -0,0 +1,15 @@
1
+ import { InputHTMLAttributes } from "react";
2
+
3
+ export interface ITTextareaProps {
4
+ value?: string;
5
+ onChange?: (value: string) => void;
6
+ label?: string;
7
+ placeholder?: string;
8
+ rows?: number;
9
+ disabled?: boolean;
10
+ error?: string;
11
+ className?: string;
12
+ name?: string;
13
+ maxLength?: number;
14
+ resize?: "none" | "vertical" | "horizontal" | "both";
15
+ }
@@ -0,0 +1,27 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import ITTextarea from "./textarea";
4
+
5
+ const meta: Meta<typeof ITTextarea> = {
6
+ title: "Components/Inputs/ITTextarea",
7
+ component: ITTextarea,
8
+ tags: ["autodocs"],
9
+ };
10
+
11
+ export default meta;
12
+ type Story = StoryObj<typeof ITTextarea>;
13
+
14
+ export const Default: Story = {
15
+ render: () => {
16
+ const [val, setVal] = useState("");
17
+ return <ITTextarea value={val} onChange={setVal} label="Descripción" placeholder="Escribe aquí..." />;
18
+ },
19
+ };
20
+
21
+ export const WithError: Story = {
22
+ args: { label: "Comentarios", value: "Mal", error: "Debe tener al menos 10 caracteres" },
23
+ };
24
+
25
+ export const Disabled: Story = {
26
+ args: { label: "Bloqueado", value: "No editable", disabled: true },
27
+ };
@@ -0,0 +1,55 @@
1
+ import clsx from "clsx";
2
+ import { ITTextareaProps } from "./textarea.props";
3
+ import { inputLabel, inputError } from "@/utils/styles";
4
+ import ITText from "@/components/text/text";
5
+
6
+ const resizeMap = {
7
+ none: "resize-none",
8
+ vertical: "resize-y",
9
+ horizontal: "resize-x",
10
+ both: "resize",
11
+ };
12
+
13
+ export default function ITTextarea({
14
+ value,
15
+ onChange,
16
+ label,
17
+ placeholder,
18
+ rows = 4,
19
+ disabled = false,
20
+ error,
21
+ className,
22
+ name,
23
+ maxLength,
24
+ resize = "vertical",
25
+ }: ITTextareaProps) {
26
+ return (
27
+ <div className={clsx("flex flex-col gap-1.5", className)}>
28
+ {label && (
29
+ <ITText as="label" className={inputLabel(!!error)} htmlFor={name}>
30
+ {label}
31
+ </ITText>
32
+ )}
33
+ <textarea
34
+ id={name}
35
+ name={name}
36
+ value={value}
37
+ onChange={(e) => onChange?.(e.target.value)}
38
+ placeholder={placeholder}
39
+ rows={rows}
40
+ disabled={disabled}
41
+ maxLength={maxLength}
42
+ className={clsx(
43
+ "w-full border border-solid transition-all duration-200 rounded-lg px-3 py-2 text-sm outline-none",
44
+ "focus:ring-2",
45
+ resizeMap[resize],
46
+ error
47
+ ? "border-red-500 ring-red-100 focus:border-red-500 focus:ring-red-100"
48
+ : "border-gray-300 focus:border-primary-500 focus:ring-primary-100",
49
+ disabled && "opacity-50 cursor-not-allowed bg-gray-100 dark:bg-slate-800"
50
+ )}
51
+ />
52
+ {error && <ITText as="span" className={inputError}>{error}</ITText>}
53
+ </div>
54
+ );
55
+ }
@@ -0,0 +1,28 @@
1
+ export interface ITThemePalette {
2
+ primary: string;
3
+ secondary: string;
4
+ ternary: string;
5
+ danger: string;
6
+ success: string;
7
+ info: string;
8
+ alert: string;
9
+ warning: string;
10
+ layout: {
11
+ sidebarBg: string;
12
+ sidebarText: string;
13
+ navbarBg: string;
14
+ navbarText: string;
15
+ };
16
+ table: {
17
+ headerBg: string;
18
+ headerText: string;
19
+ rowBg: string;
20
+ rowText: string;
21
+ };
22
+ }
23
+
24
+ export interface ITThemeProviderProps {
25
+ theme?: Partial<ITThemePalette>;
26
+ children: React.ReactNode;
27
+ showFab?: boolean;
28
+ }