@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.
- package/dist/index.css +82 -1
- package/dist/index.css.map +1 -1
- package/package.json +2 -2
- package/src/App.tsx +354 -0
- package/src/assets/logo.png +0 -0
- package/src/assets/react.svg +1 -0
- package/src/components/alert/alert.props.ts +13 -0
- package/src/components/alert/alert.stories.tsx +41 -0
- package/src/components/alert/alert.tsx +53 -0
- package/src/components/avatar/avatar.props.ts +14 -0
- package/src/components/avatar/avatar.stories.tsx +46 -0
- package/src/components/avatar/avatar.tsx +53 -0
- package/src/components/badget/badget.props.ts +12 -0
- package/src/components/badget/badget.stories.tsx +76 -0
- package/src/components/badget/badget.tsx +61 -0
- package/src/components/breadcrumbs/breadcrumbs.props.ts +13 -0
- package/src/components/breadcrumbs/breadcrumbs.stories.tsx +21 -0
- package/src/components/breadcrumbs/breadcrumbs.tsx +34 -0
- package/src/components/button/button.props.ts +18 -0
- package/src/components/button/button.stories.tsx +174 -0
- package/src/components/button/button.tsx +117 -0
- package/src/components/calendar/calendar.props.ts +33 -0
- package/src/components/calendar/calendar.stories.tsx +91 -0
- package/src/components/calendar/calendar.tsx +608 -0
- package/src/components/calendar/index.ts +3 -0
- package/src/components/card/card.props.ts +13 -0
- package/src/components/card/card.stories.tsx +58 -0
- package/src/components/card/card.tsx +79 -0
- package/src/components/checkbox/checkbox.props.ts +11 -0
- package/src/components/checkbox/checkbox.stories.tsx +54 -0
- package/src/components/checkbox/checkbox.tsx +52 -0
- package/src/components/confirm-dialog/confirm-dialog.props.ts +14 -0
- package/src/components/confirm-dialog/confirm-dialog.stories.tsx +33 -0
- package/src/components/confirm-dialog/confirm-dialog.tsx +45 -0
- package/src/components/data-table/ITDataTable.stories.tsx +213 -0
- package/src/components/data-table/dataTable.props.ts +69 -0
- package/src/components/data-table/dataTable.tsx +313 -0
- package/src/components/date-picker/date-picker.props.ts +30 -0
- package/src/components/date-picker/date-picker.stories.tsx +90 -0
- package/src/components/date-picker/datePicker.tsx +307 -0
- package/src/components/dialog/dialog.props.ts +9 -0
- package/src/components/dialog/dialog.stories.tsx +80 -0
- package/src/components/dialog/dialog.tsx +88 -0
- package/src/components/divider/divider.props.ts +8 -0
- package/src/components/divider/divider.stories.tsx +34 -0
- package/src/components/divider/divider.tsx +21 -0
- package/src/components/drawer/drawer.props.ts +14 -0
- package/src/components/drawer/drawer.stories.tsx +41 -0
- package/src/components/drawer/drawer.tsx +53 -0
- package/src/components/dropfile/dropfile.stories.tsx +75 -0
- package/src/components/dropfile/dropfile.tsx +407 -0
- package/src/components/empty-state/empty-state.props.ts +9 -0
- package/src/components/empty-state/empty-state.stories.tsx +20 -0
- package/src/components/empty-state/empty-state.tsx +21 -0
- package/src/components/flex/flex.props.ts +22 -0
- package/src/components/flex/flex.stories.tsx +71 -0
- package/src/components/flex/flex.tsx +79 -0
- package/src/components/form-builder/fieldRenderer.tsx +218 -0
- package/src/components/form-builder/formBuilder.context.tsx +70 -0
- package/src/components/form-builder/formBuilder.props.ts +43 -0
- package/src/components/form-builder/formBuilder.stories.tsx +317 -0
- package/src/components/form-builder/formBuilder.tsx +186 -0
- package/src/components/form-builder/useFormBuilder.ts +80 -0
- package/src/components/form-header/form-header.props.ts +5 -0
- package/src/components/form-header/form-header.tsx +38 -0
- package/src/components/grid/grid.props.ts +17 -0
- package/src/components/grid/grid.stories.tsx +72 -0
- package/src/components/grid/grid.tsx +69 -0
- package/src/components/image/image.props.ts +7 -0
- package/src/components/image/image.tsx +38 -0
- package/src/components/input/input.props.ts +49 -0
- package/src/components/input/input.stories.tsx +115 -0
- package/src/components/input/input.tsx +615 -0
- package/src/components/layout/layout.props.ts +10 -0
- package/src/components/layout/layout.stories.tsx +114 -0
- package/src/components/layout/layout.tsx +80 -0
- package/src/components/loader/loader.props.ts +8 -0
- package/src/components/loader/loader.stories.tsx +105 -0
- package/src/components/loader/loader.tsx +108 -0
- package/src/components/navbar/navbar.props.ts +37 -0
- package/src/components/navbar/navbar.tsx +328 -0
- package/src/components/page/page.props.ts +19 -0
- package/src/components/page/page.stories.tsx +98 -0
- package/src/components/page/page.tsx +90 -0
- package/src/components/page-header/page-header.props.ts +11 -0
- package/src/components/page-header/page-header.stories.tsx +61 -0
- package/src/components/page-header/page-header.tsx +62 -0
- package/src/components/pagination/pagination.props.ts +53 -0
- package/src/components/pagination/pagination.stories.tsx +111 -0
- package/src/components/pagination/pagination.tsx +241 -0
- package/src/components/popover/popover.props.ts +12 -0
- package/src/components/popover/popover.stories.tsx +25 -0
- package/src/components/popover/popover.tsx +45 -0
- package/src/components/progress/progress.props.ts +12 -0
- package/src/components/progress/progress.stories.tsx +40 -0
- package/src/components/progress/progress.tsx +52 -0
- package/src/components/radio/radio.props.ts +16 -0
- package/src/components/radio/radio.stories.tsx +50 -0
- package/src/components/radio/radio.tsx +58 -0
- package/src/components/search-select/index.ts +2 -0
- package/src/components/search-select/search-select.props.ts +46 -0
- package/src/components/search-select/search-select.stories.tsx +129 -0
- package/src/components/search-select/search-select.tsx +229 -0
- package/src/components/searchTable/components/EditableCell.tsx +149 -0
- package/src/components/searchTable/components/PaginationControls.tsx +86 -0
- package/src/components/searchTable/components/PaginationInfo.tsx +20 -0
- package/src/components/searchTable/components/SearchAndSortBar.tsx +53 -0
- package/src/components/searchTable/components/SearchInput.tsx +33 -0
- package/src/components/searchTable/components/SortButton.tsx +50 -0
- package/src/components/searchTable/components/TableEmptyState.tsx +22 -0
- package/src/components/searchTable/components/TableHeader.tsx +35 -0
- package/src/components/searchTable/components/TableHeaderCell.tsx +43 -0
- package/src/components/searchTable/components/TableRow.tsx +144 -0
- package/src/components/searchTable/searchTable.props.ts +56 -0
- package/src/components/searchTable/searchTable.tsx +187 -0
- package/src/components/segmented-control/segmented-control.props.ts +18 -0
- package/src/components/segmented-control/segmented-control.stories.tsx +63 -0
- package/src/components/segmented-control/segmented-control.tsx +52 -0
- package/src/components/select/select.props.ts +25 -0
- package/src/components/select/select.stories.tsx +86 -0
- package/src/components/select/select.tsx +150 -0
- package/src/components/sidebar/sidebar.props.ts +28 -0
- package/src/components/sidebar/sidebar.stories.tsx +117 -0
- package/src/components/sidebar/sidebar.tsx +313 -0
- package/src/components/skeleton/skeleton.props.ts +12 -0
- package/src/components/skeleton/skeleton.stories.tsx +30 -0
- package/src/components/skeleton/skeleton.tsx +45 -0
- package/src/components/slide/slide.props.ts +45 -0
- package/src/components/slide/slide.stories.tsx +121 -0
- package/src/components/slide/slide.tsx +109 -0
- package/src/components/slider/slider.props.ts +10 -0
- package/src/components/slider/slider.stories.tsx +30 -0
- package/src/components/slider/slider.tsx +49 -0
- package/src/components/stack/stack.props.ts +19 -0
- package/src/components/stack/stack.stories.tsx +79 -0
- package/src/components/stack/stack.tsx +79 -0
- package/src/components/stat-card/stat-card.props.ts +13 -0
- package/src/components/stat-card/stat-card.stories.tsx +41 -0
- package/src/components/stat-card/stat-card.tsx +44 -0
- package/src/components/stepper/stepper.css +26 -0
- package/src/components/stepper/stepper.props.ts +29 -0
- package/src/components/stepper/stepper.stories.tsx +155 -0
- package/src/components/stepper/stepper.tsx +227 -0
- package/src/components/table/table.props.ts +43 -0
- package/src/components/table/table.stories.tsx +189 -0
- package/src/components/table/table.tsx +376 -0
- package/src/components/tabs/tabs.props.ts +18 -0
- package/src/components/tabs/tabs.stories.tsx +32 -0
- package/src/components/tabs/tabs.tsx +74 -0
- package/src/components/text/text.props.ts +9 -0
- package/src/components/text/text.tsx +20 -0
- package/src/components/textarea/textarea.props.ts +15 -0
- package/src/components/textarea/textarea.stories.tsx +27 -0
- package/src/components/textarea/textarea.tsx +55 -0
- package/src/components/theme-provider/themeProvider.props.ts +28 -0
- package/src/components/theme-provider/themeProvider.tsx +1854 -0
- package/src/components/time-picker/timePicker.props.ts +16 -0
- package/src/components/time-picker/timePicker.stories.tsx +131 -0
- package/src/components/time-picker/timePicker.tsx +317 -0
- package/src/components/toast/toast.css +32 -0
- package/src/components/toast/toast.props.ts +13 -0
- package/src/components/toast/toast.stories.tsx +138 -0
- package/src/components/toast/toast.tsx +87 -0
- package/src/components/tooltip/tooltip.props.ts +11 -0
- package/src/components/tooltip/tooltip.stories.tsx +20 -0
- package/src/components/tooltip/tooltip.tsx +55 -0
- package/src/components/topbar/topbar.props.ts +21 -0
- package/src/components/topbar/topbar.stories.tsx +80 -0
- package/src/components/topbar/topbar.tsx +205 -0
- package/src/components/triple-filter/tripleFilter.props.ts +15 -0
- package/src/components/triple-filter/tripleFilter.stories.tsx +32 -0
- package/src/components/triple-filter/tripleFilter.tsx +50 -0
- package/src/hooks/useClickOutside.ts +21 -0
- package/src/hooks/useDebouncedSearch.ts +55 -0
- package/src/hooks/useEditableRow.ts +157 -0
- package/src/hooks/useTableState.ts +122 -0
- package/src/index.css +168 -0
- package/src/index.ts +165 -0
- package/src/main.tsx +9 -0
- package/src/showcases/DataShowcases.tsx +260 -0
- package/src/showcases/FeedbackShowcases.tsx +268 -0
- package/src/showcases/FormShowcases.tsx +1159 -0
- package/src/showcases/HomeShowcase.tsx +324 -0
- package/src/showcases/LayoutPrimitivesShowcases.tsx +569 -0
- package/src/showcases/NavigationShowcases.tsx +193 -0
- package/src/showcases/PageShowcases.tsx +207 -0
- package/src/showcases/ShowcaseLayout.tsx +139 -0
- package/src/showcases/StructureShowcases.tsx +152 -0
- package/src/types/badget.types.ts +37 -0
- package/src/types/button.types.ts +16 -0
- package/src/types/colors.types.ts +3 -0
- package/src/types/field.types.ts +103 -0
- package/src/types/formik.types.ts +15 -0
- package/src/types/input.types.ts +14 -0
- package/src/types/loader.types.ts +9 -0
- package/src/types/sizes.types.ts +1 -0
- package/src/types/table.types.ts +15 -0
- package/src/types/toast.types.ts +8 -0
- package/src/types/yup.types.ts +11 -0
- package/src/utils/color.utils.ts +99 -0
- package/src/utils/styles.ts +120 -0
- package/src/utils/table.utils.ts +10 -0
|
@@ -0,0 +1,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,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
|
+
}
|