@axzydev/axzy_ui_system 1.3.2 → 1.3.4
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.cjs +1008 -834
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.cts +53 -8
- package/dist/index.d.ts +53 -8
- package/dist/index.js +927 -753
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/molecules/table/table.doc.mdx +11 -0
- package/src/components/molecules/table/table.props.ts +30 -2
- package/src/components/molecules/table/table.stories.tsx +30 -0
- package/src/components/molecules/table/table.tsx +105 -8
- package/src/components/organisms/data-table/ITDataTable.doc.mdx +11 -2
- package/src/components/organisms/data-table/ITDataTable.stories.tsx +73 -0
- package/src/components/organisms/data-table/dataTable.props.ts +8 -4
- package/src/components/organisms/data-table/dataTable.tsx +74 -1
- package/src/components/theme-provider/themePresets.ts +6 -0
- package/src/components/theme-provider/themeProvider.doc.mdx +1 -0
- package/src/components/theme-provider/themeProvider.props.ts +2 -0
- package/src/components/theme-provider/themeProvider.tsx +13 -2
- package/src/hooks/useTableState.ts +17 -11
- package/src/index.ts +3 -3
- package/src/sandbox/LandingShowcase.tsx +416 -227
- package/src/showcases/DataShowcases.tsx +156 -21
- package/src/theme/theme.types.ts +2 -0
- package/src/types/table.types.ts +17 -0
- package/src/utils/styles.ts +3 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useState, useMemo, useCallback } from "react";
|
|
2
|
+
import { startOfDay, endOfDay } from "date-fns";
|
|
2
3
|
import {
|
|
3
4
|
ITTable,
|
|
4
5
|
ITDataTable,
|
|
@@ -47,6 +48,74 @@ const virtualUserColumns: any = [
|
|
|
47
48
|
{ key: "active", label: "Active", type: "boolean", width: "12%" },
|
|
48
49
|
];
|
|
49
50
|
|
|
51
|
+
// Demo data for the new column filter modes: adds a `hiredAt` date so the
|
|
52
|
+
// same table can showcase both single-date and date-range filters.
|
|
53
|
+
const filterDemoData = TABLE_DATA.map((row, i) => ({
|
|
54
|
+
...row,
|
|
55
|
+
hiredAt: new Date(2024, (i * 5) % 12, ((i * 7) % 27) + 1).toISOString(),
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
// Columns exercising every new filter mode: ITSearchSelect (`"search"`) and
|
|
59
|
+
// ITDatePicker in single (`"date"`) and range (`"date-range"`) modes.
|
|
60
|
+
const filterDemoColumns: any = [
|
|
61
|
+
{
|
|
62
|
+
key: "name",
|
|
63
|
+
label: "Name",
|
|
64
|
+
type: "string",
|
|
65
|
+
sortable: true,
|
|
66
|
+
filter: true,
|
|
67
|
+
render: (row: Record<string, unknown>) => (
|
|
68
|
+
<span className="font-semibold text-slate-800 dark:text-white">{row.name as string}</span>
|
|
69
|
+
),
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
key: "role",
|
|
73
|
+
label: "Role",
|
|
74
|
+
type: "string",
|
|
75
|
+
sortable: true,
|
|
76
|
+
filter: "search",
|
|
77
|
+
catalogOptions: {
|
|
78
|
+
data: [
|
|
79
|
+
{ id: "Admin", name: "Admin" },
|
|
80
|
+
{ id: "Editor", name: "Editor" },
|
|
81
|
+
{ id: "Viewer", name: "Viewer" },
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
render: (row: Record<string, unknown>) => (
|
|
85
|
+
<span className={`px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider rounded-md ${roleColor(row.role as string)}`}>
|
|
86
|
+
{row.role as string}
|
|
87
|
+
</span>
|
|
88
|
+
),
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
key: "lastLogin",
|
|
92
|
+
label: "Last login",
|
|
93
|
+
type: "date",
|
|
94
|
+
sortable: true,
|
|
95
|
+
filter: "date",
|
|
96
|
+
render: (row: Record<string, unknown>) =>
|
|
97
|
+
new Date(row.lastLogin as string).toLocaleDateString("es-MX", {
|
|
98
|
+
day: "2-digit",
|
|
99
|
+
month: "short",
|
|
100
|
+
year: "numeric",
|
|
101
|
+
}),
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
key: "hiredAt",
|
|
105
|
+
label: "Hired",
|
|
106
|
+
type: "date",
|
|
107
|
+
sortable: true,
|
|
108
|
+
filter: "date-range",
|
|
109
|
+
dateFilterOptions: { maxDate: new Date() },
|
|
110
|
+
render: (row: Record<string, unknown>) =>
|
|
111
|
+
new Date(row.hiredAt as string).toLocaleDateString("es-MX", {
|
|
112
|
+
day: "2-digit",
|
|
113
|
+
month: "short",
|
|
114
|
+
year: "numeric",
|
|
115
|
+
}),
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
|
|
50
119
|
const nameColumn = {
|
|
51
120
|
key: "name",
|
|
52
121
|
label: "Name",
|
|
@@ -276,6 +345,9 @@ export const TableShowcase = () => {
|
|
|
276
345
|
'<ITTable columns={columns} data={data} defaultView="cards" onRowClick={(row) => open(row)} />',
|
|
277
346
|
'<ITTable columns={fixedColumns} data={data} layout="fixed" density="compact" />',
|
|
278
347
|
'<ITTable columns={cols} data={rows} virtualized stickyHeader layout="fixed" density="compact" defaultItemsPerPage={300} itemsPerPageOptions={[50, 100, 300]} />',
|
|
348
|
+
'// ITSearchSelect typeahead (server-side search opcional vía catalogOptions.onSearch)\n{ key: "role", label: "Role", type: "string", filter: "search", catalogOptions: { data: roles } }',
|
|
349
|
+
'// ITDatePicker fecha única\n{ key: "lastLogin", label: "Last login", type: "date", filter: "date", dateFilterOptions: { maxDate: new Date() } }',
|
|
350
|
+
'// ITDatePicker rango\n{ key: "hiredAt", label: "Hired", type: "date", filter: "date-range", dateFilterOptions: { maxDate: new Date() } }',
|
|
279
351
|
],
|
|
280
352
|
props: [
|
|
281
353
|
{ name: "columns", type: "Column<T>[]", description: "Definiciones de columna: key, label, type, sortable, filter, render, actions." },
|
|
@@ -309,6 +381,7 @@ export const TableShowcase = () => {
|
|
|
309
381
|
"Virtualización: solo vista tabla; rinde con page size grande; filas de alto uniforme; el scroll no se reinicia al cambiar filtros.",
|
|
310
382
|
"Virtualización exige alto de fila uniforme. El default es size-aware (getRowHeight(size, density)): con size=\"md\" el baseline es compact 33 / normal 45 / comfortable 53 (padding de celda + line box text-sm de 20px + 1px del divide-y); size lo desplaza por el line box (sm 16 / md 20 / lg 28 px, es decir −4 / 0 / +8 px). Una celda de dos líneas (avatar + texto) mide ~50px real, distinto de ese alto asumido, así que los spacers se desalinean: usar celdas de una sola línea o pasar un rowHeight explícito.",
|
|
311
383
|
"ITSearchTable no soporta layout/density/autoCardBreakpoint/virtualized/width — no asumir paridad.",
|
|
384
|
+
"Nuevos modos de filtro por columna: filter: \"search\" renderiza ITSearchSelect (usa catalogOptions.data; opcional catalogOptions.onSearch para búsqueda en servidor), y filter: \"date\" / \"date-range\" renderizan ITDatePicker (bounds vía dateFilterOptions). El rango se guarda como [start, end] y se considera vacío cuando ambos extremos son null.",
|
|
312
385
|
],
|
|
313
386
|
}}
|
|
314
387
|
gallery={
|
|
@@ -440,6 +513,22 @@ export const TableShowcase = () => {
|
|
|
440
513
|
/>
|
|
441
514
|
</div>
|
|
442
515
|
</div>
|
|
516
|
+
|
|
517
|
+
<div>
|
|
518
|
+
<h4 className="text-sm font-bold text-slate-700 dark:text-slate-200 mb-3">Column filters: ITSearchSelect + ITDatePicker (single & range)</h4>
|
|
519
|
+
<div className="border border-slate-200 dark:border-slate-700 rounded-xl overflow-hidden">
|
|
520
|
+
<ITTable
|
|
521
|
+
columns={filterDemoColumns}
|
|
522
|
+
data={filterDemoData}
|
|
523
|
+
title="Filter demo"
|
|
524
|
+
size="sm"
|
|
525
|
+
defaultItemsPerPage={8}
|
|
526
|
+
/>
|
|
527
|
+
</div>
|
|
528
|
+
<p className="mt-2 text-xs text-slate-500 dark:text-slate-400">
|
|
529
|
+
Role uses <code>filter: "search"</code> (ITSearchSelect). Last login uses <code>filter: "date"</code> and Hired uses <code>filter: "date-range"</code> (ITDatePicker), with <code>dateFilterOptions</code> bounds.
|
|
530
|
+
</p>
|
|
531
|
+
</div>
|
|
443
532
|
</div>
|
|
444
533
|
}
|
|
445
534
|
/>
|
|
@@ -475,8 +564,15 @@ export const DataTableShowcase = () => {
|
|
|
475
564
|
key: "role",
|
|
476
565
|
label: "Role",
|
|
477
566
|
type: "string" as const,
|
|
478
|
-
filter:
|
|
567
|
+
filter: "search" as const,
|
|
479
568
|
sortable: true,
|
|
569
|
+
catalogOptions: {
|
|
570
|
+
data: [
|
|
571
|
+
{ id: "Admin", name: "Admin" },
|
|
572
|
+
{ id: "Editor", name: "Editor" },
|
|
573
|
+
{ id: "Viewer", name: "Viewer" },
|
|
574
|
+
],
|
|
575
|
+
},
|
|
480
576
|
render: (row: any) => (
|
|
481
577
|
<span className={`px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider rounded-md ${
|
|
482
578
|
row.role === "Admin" ? "bg-primary-100 text-primary-700 dark:bg-primary-950/40 dark:text-primary-300" :
|
|
@@ -485,23 +581,42 @@ export const DataTableShowcase = () => {
|
|
|
485
581
|
}`}>{row.role}</span>
|
|
486
582
|
)
|
|
487
583
|
},
|
|
584
|
+
{
|
|
585
|
+
key: "hiredAt",
|
|
586
|
+
label: "Hired",
|
|
587
|
+
type: "date" as const,
|
|
588
|
+
filter: "date-range" as const,
|
|
589
|
+
sortable: true,
|
|
590
|
+
dateFilterOptions: { maxDate: new Date() },
|
|
591
|
+
render: (row: any) =>
|
|
592
|
+
new Date(row.hiredAt as string).toLocaleDateString("es-MX", {
|
|
593
|
+
day: "2-digit",
|
|
594
|
+
month: "short",
|
|
595
|
+
year: "numeric",
|
|
596
|
+
}),
|
|
597
|
+
},
|
|
488
598
|
{ key: "active", label: "Active", type: "boolean" as const, filter: true }
|
|
489
599
|
], []);
|
|
490
600
|
|
|
491
|
-
const MOCK_USERS = useMemo(() =>
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
601
|
+
const MOCK_USERS = useMemo(() =>
|
|
602
|
+
[
|
|
603
|
+
{ id: 1, name: "Sofía Castillo", role: "Admin", active: true },
|
|
604
|
+
{ id: 2, name: "Daniela Klein", role: "Editor", active: true },
|
|
605
|
+
{ id: 3, name: "Mariana Reyes", role: "Viewer", active: false },
|
|
606
|
+
{ id: 4, name: "Camila Torres", role: "Admin", active: true },
|
|
607
|
+
{ id: 5, name: "Valentina Méndez", role: "Editor", active: true },
|
|
608
|
+
{ id: 6, name: "Luciana Rivas", role: "Viewer", active: false },
|
|
609
|
+
{ id: 7, name: "Isabella Cruz", role: "Editor", active: true },
|
|
610
|
+
{ id: 8, name: "Gabriela Vargas", role: "Admin", active: true },
|
|
611
|
+
{ id: 9, name: "Ana López", role: "Editor", active: false },
|
|
612
|
+
{ id: 10, name: "Carla Jiménez", role: "Viewer", active: true },
|
|
613
|
+
{ id: 11, name: "Luisa Fernández", role: "Admin", active: true },
|
|
614
|
+
{ id: 12, name: "Renata Morales", role: "Viewer", active: false },
|
|
615
|
+
].map((row, i) => ({
|
|
616
|
+
...row,
|
|
617
|
+
hiredAt: new Date(2023, (i * 5) % 12, ((i * 7) % 27) + 1).toISOString(),
|
|
618
|
+
})),
|
|
619
|
+
[]);
|
|
505
620
|
|
|
506
621
|
const fetchData = useCallback(async (params: ITDataTableFetchParams): Promise<ITDataTableResponse<any>> => {
|
|
507
622
|
const delay = apiState === "loading" ? 8000 : 400;
|
|
@@ -517,14 +632,31 @@ export const DataTableShowcase = () => {
|
|
|
517
632
|
|
|
518
633
|
let mockData = [...MOCK_USERS];
|
|
519
634
|
|
|
635
|
+
const matchesFilter = (itemValue: unknown, filterVal: unknown): boolean => {
|
|
636
|
+
// Single date (filter: "date")
|
|
637
|
+
if (filterVal instanceof Date) {
|
|
638
|
+
const d = new Date(String(itemValue));
|
|
639
|
+
return !isNaN(d.getTime()) && d.toDateString() === filterVal.toDateString();
|
|
640
|
+
}
|
|
641
|
+
// Date range (filter: "date-range") — [start, end]
|
|
642
|
+
if (Array.isArray(filterVal)) {
|
|
643
|
+
const [start, end] = filterVal as [Date | null, Date | null];
|
|
644
|
+
if (!start && !end) return true;
|
|
645
|
+
const d = new Date(String(itemValue));
|
|
646
|
+
if (isNaN(d.getTime())) return false;
|
|
647
|
+
if (start && d.getTime() < startOfDay(start).getTime()) return false;
|
|
648
|
+
if (end && d.getTime() > endOfDay(end).getTime()) return false;
|
|
649
|
+
return true;
|
|
650
|
+
}
|
|
651
|
+
// Scalar filters (text/number/boolean/catalog/search id)
|
|
652
|
+
return String(itemValue).toLowerCase().includes(String(filterVal).toLowerCase());
|
|
653
|
+
};
|
|
654
|
+
|
|
520
655
|
if (params.filters) {
|
|
521
656
|
Object.keys(params.filters).forEach(key => {
|
|
522
657
|
const filterVal = params.filters[key];
|
|
523
|
-
if (filterVal)
|
|
524
|
-
|
|
525
|
-
String((item as any)[key]).toLowerCase().includes(String(filterVal).toLowerCase())
|
|
526
|
-
);
|
|
527
|
-
}
|
|
658
|
+
if (filterVal === undefined || filterVal === "") return;
|
|
659
|
+
mockData = mockData.filter(item => matchesFilter((item as any)[key], filterVal));
|
|
528
660
|
});
|
|
529
661
|
}
|
|
530
662
|
|
|
@@ -745,12 +877,14 @@ export const DataTableShowcase = () => {
|
|
|
745
877
|
'<ITDataTable columns={fixedColumns} fetchData={api.fetchUsers} layout="fixed" density="compact" />',
|
|
746
878
|
'<ITDataTable columns={cols} fetchData={api.page} virtualized stickyHeader defaultItemsPerPage={200} itemsPerPageOptions={[50, 100, 200]} debounceMs={0} />',
|
|
747
879
|
'<ITDataTable columns={cols} fetchData={api.page} layout="fixed" density="compact" autoCardBreakpoint={640} />',
|
|
880
|
+
'// Filtro de columna tipo ITSearchSelect (typeahead)\n{ key: "role", label: "Role", type: "string", filter: "search", catalogOptions: { data: roles, onSearch: (q) => api.searchRoles(q) } }',
|
|
881
|
+
'// Filtro de columna tipo ITDatePicker (rango) — params.filters[key] llega como [start, end]\n{ key: "hiredAt", label: "Hired", type: "date", filter: "date-range", dateFilterOptions: { maxDate: new Date() } }',
|
|
748
882
|
],
|
|
749
883
|
props: [
|
|
750
884
|
{ name: "columns", type: "Column<T>[]", description: "Definiciones de columna." },
|
|
751
885
|
{ name: "fetchData", type: "(params: ITDataTableFetchParams) => Promise<ITDataTableResponse<T>>", required: true, description: "Callback asíncrono disparado al cambiar página, filtros u orden. Retorna { data, total }." },
|
|
752
886
|
{ name: "debounceMs", type: "number", default: "500", description: "Espera antes de refetch tras cambiar filtros." },
|
|
753
|
-
{ name: "externalFilters", type: "
|
|
887
|
+
{ name: "externalFilters", type: "ColumnFilters", default: "{}", description: "Filtros externos fusionados con los internos. Los valores de fecha pueden ser Date o [start, end]." },
|
|
754
888
|
{ name: "loadingIndicator", type: "ReactNode", description: "Elemento custom mostrado mientras carga." },
|
|
755
889
|
{ name: "fetchOnMount", type: "boolean", default: "true", description: "Dispara fetch al montar." },
|
|
756
890
|
{ name: "reloadTrigger", type: "number | string | boolean", description: "Cambiar su valor fuerza un refetch." },
|
|
@@ -787,6 +921,7 @@ export const DataTableShowcase = () => {
|
|
|
787
921
|
"El toggle interno Table/Cards también desactiva la virtualización; las cards nunca se virtualizan.",
|
|
788
922
|
"debounceMs={0} evita el retardo de refetch en demos.",
|
|
789
923
|
"variant=\"minimal\" se acepta por tipo pero no tiene estilos (igual que default). ITSearchTable no soporta layout/density/autoCardBreakpoint/virtualized/width.",
|
|
924
|
+
"Modos de filtro por columna: filter: \"search\" renderiza ITSearchSelect (usa catalogOptions.data; catalogOptions.onSearch habilita búsqueda server-side debounced), y filter: \"date\" / \"date-range\" renderizan ITDatePicker con dateFilterOptions { minDate, maxDate }. En fetchData, params.filters[key] llega como Date (date) o [start, end] (date-range).",
|
|
790
925
|
],
|
|
791
926
|
}}
|
|
792
927
|
gallery={
|
package/src/theme/theme.types.ts
CHANGED
|
@@ -50,6 +50,8 @@ export interface ITThemePalette {
|
|
|
50
50
|
rowBg: string;
|
|
51
51
|
/** Text color of table body rows. @default "#1e293b" */
|
|
52
52
|
rowText: string;
|
|
53
|
+
/** Background color applied to a table body row while the pointer is over it (hex). @default "#f1f5f9" */
|
|
54
|
+
rowHover: string;
|
|
53
55
|
};
|
|
54
56
|
}
|
|
55
57
|
|
package/src/types/table.types.ts
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
export type TableVariants = "default" | "striped" | "bordered";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Value stored for a single column filter.
|
|
5
|
+
*
|
|
6
|
+
* - `string | number | boolean` — text/number/boolean filters and catalog ids.
|
|
7
|
+
* - `Date` — a single-date filter (`Column.filter: "date"`).
|
|
8
|
+
* - `[Date | null, Date | null]` — a date-range filter (`Column.filter: "date-range"`).
|
|
9
|
+
*/
|
|
10
|
+
export type ColumnFilterValue =
|
|
11
|
+
| string
|
|
12
|
+
| number
|
|
13
|
+
| boolean
|
|
14
|
+
| Date
|
|
15
|
+
| [Date | null, Date | null];
|
|
16
|
+
|
|
17
|
+
/** Map of column `key` to its active filter value. */
|
|
18
|
+
export type ColumnFilters = Record<string, ColumnFilterValue>;
|
|
19
|
+
|
|
3
20
|
export type TableSize = "sm" | "md" | "lg";
|
|
4
21
|
|
|
5
22
|
export const variantStyles: Record<TableVariants, string> = {
|
package/src/utils/styles.ts
CHANGED
|
@@ -86,12 +86,14 @@ export const tableSortButtonInactive = "hover:bg-secondary-200 text-secondary-40
|
|
|
86
86
|
|
|
87
87
|
export const tableBody = "divide-y divide-secondary-100";
|
|
88
88
|
|
|
89
|
-
export const tableRow = "hover:bg-secondary-50
|
|
89
|
+
export const tableRow = "hover:bg-[var(--color-table-rowHover,var(--color-secondary-50))] transition-colors duration-150 group";
|
|
90
90
|
|
|
91
91
|
export const tableRowClickable = "cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-inset";
|
|
92
92
|
|
|
93
93
|
export const tableCardClickable = "cursor-pointer rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500";
|
|
94
94
|
|
|
95
|
+
export const tableCardHover = "hover:bg-[var(--color-table-rowHover,var(--color-secondary-50))] transition-colors duration-150";
|
|
96
|
+
|
|
95
97
|
export function tableCell(className?: string, density: TableDensity = "normal") {
|
|
96
98
|
return clsx(densityStyles[density].cell, "align-middle", className);
|
|
97
99
|
}
|