@axzydev/axzy_ui_system 1.2.3 → 1.2.6

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/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "packageManager": "pnpm@10.34.0",
5
5
  "private": false,
6
6
  "license": "MIT",
7
- "version": "1.2.3",
7
+ "version": "1.2.6",
8
8
  "type": "module",
9
9
  "sideEffects": [
10
10
  "*.css"
@@ -29,9 +29,10 @@
29
29
  "dev": "vite",
30
30
  "build": "tsc -b && vite build",
31
31
  "build:app": "tsc -b && vite build -c vite.app.config.ts",
32
+ "build:css": "node scripts/build-css.mjs",
32
33
  "lint": "eslint .",
33
34
  "preview": "vite preview",
34
- "bundle": "tsup src/index.ts",
35
+ "bundle": "tsup src/index.ts && node scripts/build-css.mjs",
35
36
  "watch": "tsup src/index.ts --watch",
36
37
  "storybook": "storybook dev -p 6006",
37
38
  "build-storybook": "storybook build"
package/src/App.tsx CHANGED
@@ -1,7 +1,7 @@
1
1
  import React, { useMemo, useState } from "react";
2
2
  import {
3
3
  FaCreditCard,
4
- FaHome,
4
+ FaHome,
5
5
  FaKeyboard,
6
6
  FaRegBell,
7
7
  FaSearch,
@@ -196,7 +196,7 @@ export default function ITDataTable<T extends Record<string, unknown>>({
196
196
 
197
197
  return (
198
198
  <div className={clsx("space-y-4 w-full relative", containerClassName)}>
199
- <div className="rounded-xl shadow-sm border border-secondary-200 overflow-hidden" style={{ backgroundColor: 'var(--color-table-rowBg, #ffffff)' }}>
199
+ <div className="rounded-xl shadow-sm overflow-hidden" style={{ backgroundColor: 'var(--color-table-rowBg, #ffffff)' }}>
200
200
  {title && (
201
201
  <div className="px-6 py-5 border-b border-secondary-100 flex justify-between items-center" style={{ backgroundColor: 'var(--color-table-rowBg, #ffffff)' }}>
202
202
  <ITText as="h2" className="text-xl font-bold text-secondary-900 leading-tight">{title}</ITText>
@@ -232,7 +232,7 @@ export default function ITDataTable<T extends Record<string, unknown>>({
232
232
  )}
233
233
  >
234
234
  <thead>
235
- <tr className="bg-secondary-50 border-b border-secondary-200 text-xs uppercase tracking-wider font-semibold text-secondary-500">
235
+ <tr className="bg-white border-b border-secondary-200 text-xs uppercase tracking-wider font-semibold text-secondary-500">
236
236
  {columns.map((col) => (
237
237
  <th key={col.key} scope="col" className={clsx("px-4 py-4 align-top", col.className)}>
238
238
  <div className="flex flex-col gap-3 min-w-[150px]">
@@ -1,4 +1,5 @@
1
1
  import { useState } from "react";
2
+ import clsx from "clsx";
2
3
  import ITTopBar from "../topbar/topbar";
3
4
  import ITSidebar from "../sidebar/sidebar";
4
5
  import { ITLayoutProps } from "./layout.props";
@@ -69,7 +70,7 @@ export default function ITLayout({
69
70
 
70
71
  {/* MAIN CONTENT AREA */}
71
72
  <main className="flex-1 overflow-y-auto w-full custom-scrollbar relative z-0">
72
- <div className={`mx-auto w-full h-full ${contentClassName}`}>
73
+ <div className={clsx("mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8 py-6 h-full", contentClassName)}>
73
74
  {children}
74
75
  </div>
75
76
  </main>
@@ -9,6 +9,8 @@ export interface ITPageProps {
9
9
  backAction?: () => void;
10
10
  loading?: boolean;
11
11
  error?: string | null;
12
+ errorTitle?: string;
13
+ errorActionLabel?: string;
12
14
  onRetry?: () => void;
13
15
  empty?: boolean;
14
16
  emptyTitle?: string;
@@ -6,51 +6,66 @@ import ITEmptyState from "../empty-state/empty-state";
6
6
  import ITButton from "../button/button";
7
7
  import ITStack from "../stack/stack";
8
8
 
9
- export default function ITPage({
10
- title,
11
- description,
12
- breadcrumbs,
13
- actions,
14
- backAction,
15
- loading = false,
16
- error = null,
17
- onRetry,
18
- empty = false,
19
- emptyTitle,
20
- emptyDescription,
21
- emptyAction,
22
- className,
23
- children,
24
- }: ITPageProps) {
9
+ const hasHeader = (props: ITPageProps) =>
10
+ !!(props.title || props.description || props.breadcrumbs || props.actions || props.backAction);
11
+
12
+ const renderHeader = (props: ITPageProps) => {
13
+ if (!hasHeader(props)) return null;
14
+ return (
15
+ <ITPageHeader
16
+ title={props.title || ""}
17
+ description={props.description}
18
+ breadcrumbs={props.breadcrumbs}
19
+ actions={props.actions}
20
+ backAction={props.backAction}
21
+ />
22
+ );
23
+ };
24
+
25
+ export default function ITPage(props: ITPageProps) {
26
+ const {
27
+ loading = false,
28
+ error = null,
29
+ errorTitle,
30
+ errorActionLabel,
31
+ onRetry,
32
+ empty = false,
33
+ emptyTitle,
34
+ emptyDescription,
35
+ emptyAction,
36
+ className,
37
+ children,
38
+ } = props;
39
+
40
+ const wrapperClass = clsx("space-y-6", className);
41
+
25
42
  if (loading) {
26
43
  return (
27
- <div className={className}>
28
- {title && (
29
- <ITPageHeader title={title} />
30
- )}
31
- <div className="mt-6">
32
- <ITStack spacing={4}>
33
- <ITSkeleton variant="rectangular" height={40} width="40%" />
34
- <ITSkeleton variant="rectangular" height={200} />
35
- <ITSkeleton variant="rectangular" height={200} />
36
- </ITStack>
37
- </div>
44
+ <div className={wrapperClass}>
45
+ {renderHeader(props)}
46
+ <ITStack spacing={4}>
47
+ <ITSkeleton variant="rectangular" height={40} width="40%" />
48
+ <ITSkeleton variant="rectangular" height={200} />
49
+ <ITSkeleton variant="rectangular" height={200} />
50
+ </ITStack>
38
51
  </div>
39
52
  );
40
53
  }
41
54
 
42
55
  if (error) {
43
56
  return (
44
- <div className={className}>
45
- {title && (
46
- <ITPageHeader title={title} />
47
- )}
57
+ <div className={wrapperClass}>
58
+ {renderHeader(props)}
48
59
  <ITEmptyState
49
- title="Error"
60
+ title={errorTitle || "Error"}
50
61
  description={error}
51
62
  action={
52
63
  onRetry ? (
53
- <ITButton label="Reintentar" onClick={onRetry} size="small" />
64
+ <ITButton
65
+ label={errorActionLabel || "Reintentar"}
66
+ onClick={onRetry}
67
+ size="small"
68
+ />
54
69
  ) : undefined
55
70
  }
56
71
  />
@@ -60,10 +75,8 @@ export default function ITPage({
60
75
 
61
76
  if (empty) {
62
77
  return (
63
- <div className={className}>
64
- {title && (
65
- <ITPageHeader title={title} />
66
- )}
78
+ <div className={wrapperClass}>
79
+ {renderHeader(props)}
67
80
  <ITEmptyState
68
81
  title={emptyTitle || "Sin datos"}
69
82
  description={emptyDescription || "No hay información para mostrar"}
@@ -74,16 +87,8 @@ export default function ITPage({
74
87
  }
75
88
 
76
89
  return (
77
- <div className={clsx("space-y-6", className)}>
78
- {(title || breadcrumbs || actions || backAction) && (
79
- <ITPageHeader
80
- title={title || ""}
81
- description={description}
82
- breadcrumbs={breadcrumbs}
83
- actions={actions}
84
- backAction={backAction}
85
- />
86
- )}
90
+ <div className={wrapperClass}>
91
+ {renderHeader(props)}
87
92
  {children}
88
93
  </div>
89
94
  );
@@ -1,4 +1,3 @@
1
- import clsx from "clsx";
2
1
  import { FaChevronLeft } from "react-icons/fa";
3
2
  import { ITPageHeaderProps } from "./page-header.props";
4
3
  import ITBreadcrumbs from "../breadcrumbs/breadcrumbs";
@@ -15,7 +14,7 @@ export default function ITPageHeader({
15
14
  const showTopRow = breadcrumbs?.length || backAction;
16
15
 
17
16
  return (
18
- <div className={clsx(className)}>
17
+ <div className={className}>
19
18
  {showTopRow && (
20
19
  <div className="flex items-center justify-between gap-4 mb-1">
21
20
  <div className="flex items-center gap-2 min-w-0">
@@ -68,7 +68,7 @@ export default function ITTable<T extends Record<string, unknown>>({
68
68
  comparison =
69
69
  new Date(aValue as string).getTime() -
70
70
  new Date(bValue as string).getTime();
71
- break;
71
+ break;
72
72
  case "boolean":
73
73
  comparison = aValue === bValue ? 0 : aValue ? 1 : -1;
74
74
  break;
@@ -142,48 +142,48 @@ export default function ITTable<T extends Record<string, unknown>>({
142
142
  currentValue === undefined
143
143
  ? true
144
144
  : currentValue === true
145
- ? false
146
- : undefined;
145
+ ? false
146
+ : undefined;
147
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
- };
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
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
- );
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
175
  }
176
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
- }
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
187
 
188
188
  if (col.catalogOptions.error) {
189
189
  return <ITText as="span" className="text-red-500 text-xs">Error cargando</ITText>;
@@ -204,7 +204,7 @@ export default function ITTable<T extends Record<string, unknown>>({
204
204
  const value = e.target.value === "" ? undefined : e.target.value;
205
205
  handleFilterChange(col.key, value);
206
206
  }}
207
- onBlur={() => {}}
207
+ onBlur={() => { }}
208
208
  className="w-full text-xs"
209
209
  />
210
210
  );
@@ -217,7 +217,7 @@ export default function ITTable<T extends Record<string, unknown>>({
217
217
  placeholder="Buscar..."
218
218
  value={String(filters[col.key] || "")}
219
219
  onChange={(e) => handleFilterChange(col.key, e.target.value)}
220
- onBlur={() => {}}
220
+ onBlur={() => { }}
221
221
  />
222
222
  );
223
223
  };
@@ -230,22 +230,22 @@ export default function ITTable<T extends Record<string, unknown>>({
230
230
  }
231
231
 
232
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
- );
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
249
  case "actions":
250
250
  return col.actions ? col.actions(row) : null;
251
251
  case "catalog":
@@ -261,116 +261,115 @@ export default function ITTable<T extends Record<string, unknown>>({
261
261
  }
262
262
  };
263
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]
264
+ return (
265
+ <div className={clsx("space-y-4 w-full", containerClassName)}>
266
+ <div className="rounded-xl shadow-sm 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>
281
272
  )}
282
- >
283
273
 
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">
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
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">
296
+ {col.sortable && col.type !== "actions" && (
297
+ <button
298
+ onClick={() => handleSort(col.key)}
299
+ className={`p-1 rounded-md transition-colors ${sortConfig?.key === col.key
300
+ ? "bg-secondary-200 text-secondary-900"
301
+ : "hover:bg-secondary-200 text-secondary-400 hover:text-secondary-700"
302
+ }`}
303
+ title={`Ordenar por ${col.label}`}
304
+ >
305
+ <MdOutlineSwapVert className="w-4 h-4" aria-hidden="true" />
306
+ </button>
307
+ )}
308
+ </div>
309
+
310
+ {/* Filter section */}
311
+ <div className="w-full">
312
+ {col.filter ? renderFilterInput(col) : null}
313
+ </div>
314
+ </div>
315
+ </th>
316
+ ))}
317
+ </tr>
318
+ </thead>
319
+ <tbody className="divide-y divide-secondary-100">
320
+ {currentData.length > 0 ? (
321
+ currentData.map((row, rowIndex) => (
322
+ <tr
323
+ key={rowIndex}
324
+ className="hover:bg-secondary-50/50 transition-colors duration-150 group"
325
+ >
326
+ {columns.map((col) => (
327
+ <td
328
+ key={`${rowIndex}-${col.key}`}
329
+ className={clsx("px-4 py-3 align-middle", col.className)}
330
+ >
331
+ {col.type === "actions" ? (
332
+ <div className="flex items-center justify-center gap-2">
338
333
  {renderCellContent(col, row) as React.ReactNode}
339
- </div>
340
- )}
341
- </td>
342
- ))}
334
+ </div>
335
+ ) : (
336
+ <div className="text-secondary-700 font-medium">
337
+ {renderCellContent(col, row) as React.ReactNode}
338
+ </div>
339
+ )}
340
+ </td>
341
+ ))}
342
+ </tr>
343
+ ))
344
+ ) : (
345
+ <tr>
346
+ <td colSpan={columns.length} className="px-6 py-12 text-center">
347
+ <div className="flex flex-col items-center justify-center text-secondary-400">
348
+ <ITText as="span" className="text-lg">No se encontraron resultados</ITText>
349
+ <ITText as="span" className="text-sm mt-1">Intenta ajustar los filtros</ITText>
350
+ </div>
351
+ </td>
343
352
  </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>
353
+ )}
354
+ </tbody>
355
+ </table>
356
+ </div>
358
357
 
359
358
 
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
- />
359
+ {/* Pagination */}
360
+ <div className="rounded-b-xl border-t border-secondary-200 px-6 py-4" style={{ backgroundColor: 'var(--color-table-rowBg, #ffffff)' }}>
361
+ <ITPagination
362
+ currentPage={currentPage}
363
+ totalPages={computedTotalPages}
364
+ onPageChange={goToPage}
365
+ color="primary"
366
+ itemsPerPageOptions={itemsPerPageOptions}
367
+ itemsPerPage={itemsPerPage}
368
+ onItemsPerPageChange={handleItemsPerPageChange}
369
+ totalItems={filteredData.length}
370
+ />
371
+ </div>
372
372
  </div>
373
373
  </div>
374
- </div>
375
374
  );
376
375
  }
package/src/index.css CHANGED
@@ -1,3 +1,8 @@
1
+ @import "tailwindcss";
2
+
3
+ @source "./**/*.{ts,tsx}";
4
+ @source "../.storybook/**/*.{ts,tsx}";
5
+
1
6
  @variant dark (&:is(.dark &));
2
7
 
3
8
  :root, :host {
@@ -49,7 +49,7 @@ export const TableShowcase = () => {
49
49
  description="Base de renderizado de tablas estructuradas con soporte de alineamiento y tipado estricto."
50
50
  code={code}
51
51
  demo={
52
- <div className="w-full bg-white dark:bg-slate-900 border rounded-xl overflow-hidden shadow-sm">
52
+ <div className="w-full bg-white dark:bg-slate-900 rounded-xl overflow-hidden shadow-sm">
53
53
  <ITTable
54
54
  columns={columns}
55
55
  data={data}