@axzydev/axzy_ui_system 1.4.7 → 1.4.8

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.
@@ -85,6 +85,26 @@ export default function ITDataTable<T extends Record<string, unknown>>({
85
85
  rowHeight,
86
86
  overscan = 5,
87
87
  stickyHeader = false,
88
+ showViewToggle = true,
89
+ design,
90
+ colors,
91
+ containerStyle,
92
+ cardClassName,
93
+ cardStyle,
94
+ toolbarClassName,
95
+ toolbarStyle,
96
+ headerClassName,
97
+ headerStyle,
98
+ headerCellClassName,
99
+ headerCellStyle,
100
+ rowClassName,
101
+ rowStyle,
102
+ cellClassName,
103
+ cellStyle,
104
+ cardsClassName,
105
+ cardsStyle,
106
+ footerClassName,
107
+ footerStyle,
88
108
  }: ITDataTableProps<T>) {
89
109
  const [viewMode, setViewMode] = useState<"table" | "cards">(defaultView || "table");
90
110
  const { ref: rootRef, width: containerWidth } = useElementSize<HTMLDivElement>();
@@ -92,6 +112,27 @@ export default function ITDataTable<T extends Record<string, unknown>>({
92
112
  const [totalItems, setTotalItems] = useState(0);
93
113
  const [isLoading, setIsLoading] = useState(fetchOnMount);
94
114
 
115
+ // Per-instance color overrides written as local `--color-table-*` variables.
116
+ // Declared on the wrapper (an ancestor), they beat the global theme's
117
+ // `:root` values for this subtree only.
118
+ const colorVars = {
119
+ ...(colors?.headerBg ? { "--color-table-headerBg": colors.headerBg } : {}),
120
+ ...(colors?.headerText ? { "--color-table-headerText": colors.headerText } : {}),
121
+ ...(colors?.rowBg ? { "--color-table-rowBg": colors.rowBg } : {}),
122
+ ...(colors?.rowText ? { "--color-table-rowText": colors.rowText } : {}),
123
+ ...(colors?.rowHover ? { "--color-table-rowHover": colors.rowHover } : {}),
124
+ } as React.CSSProperties;
125
+
126
+ /** Merge a `design` slot with its flat shortcut (flat wins) into className/style. */
127
+ const mergeSlot = (
128
+ slot: { className?: string; style?: React.CSSProperties } | undefined,
129
+ flatClassName?: string,
130
+ flatStyle?: React.CSSProperties
131
+ ) => ({
132
+ className: clsx(slot?.className, flatClassName),
133
+ style: { ...slot?.style, ...flatStyle } as React.CSSProperties,
134
+ });
135
+
95
136
  const isFixed = layout === "fixed";
96
137
  const forcedCards =
97
138
  autoCardBreakpoint > 0 && containerWidth > 0 && containerWidth < autoCardBreakpoint;
@@ -409,6 +450,29 @@ export default function ITDataTable<T extends Record<string, unknown>>({
409
450
  onRowClick(row, event);
410
451
  };
411
452
 
453
+ const containerSlot = mergeSlot(design?.container, containerClassName, containerStyle);
454
+ const cardSlot = mergeSlot(design?.card, cardClassName, cardStyle);
455
+ const toolbarSlot = mergeSlot(design?.toolbar, toolbarClassName, toolbarStyle);
456
+ const headerSlot = mergeSlot(design?.header, headerClassName, headerStyle);
457
+ const headerCellSlot = mergeSlot(design?.headerCell, headerCellClassName, headerCellStyle);
458
+ const rowSlot = mergeSlot(design?.row, rowClassName, rowStyle);
459
+ const cellSlot = mergeSlot(design?.cell, cellClassName, cellStyle);
460
+ const cardsSlot = mergeSlot(design?.cards, cardsClassName, cardsStyle);
461
+ const footerSlot = mergeSlot(design?.footer, footerClassName, footerStyle);
462
+
463
+ const viewToggle = (
464
+ <div className="flex items-center bg-slate-100 dark:bg-slate-800/80 rounded-lg p-0.5 border border-slate-200 dark:border-slate-700/50">
465
+ <button onClick={() => setViewMode("table")} disabled={forcedCards} className={segCtrlClass("table")}>
466
+ <FaTable size={11} />
467
+ Table
468
+ </button>
469
+ <button onClick={() => setViewMode("cards")} className={segCtrlClass("cards")}>
470
+ <FaThLarge size={11} />
471
+ Cards
472
+ </button>
473
+ </div>
474
+ );
475
+
412
476
  const tableEl = (
413
477
  <table
414
478
  className={clsx(
@@ -439,7 +503,8 @@ export default function ITDataTable<T extends Record<string, unknown>>({
439
503
  )}
440
504
  <thead>
441
505
  <tr
442
- className={clsx(tableHeaderRow, "dark:text-slate-200")}
506
+ className={clsx(tableHeaderRow, "dark:text-slate-200", headerSlot.className)}
507
+ style={headerSlot.style}
443
508
  aria-rowindex={isTableVirtual ? 1 : undefined}
444
509
  >
445
510
  {columns.map((col) => {
@@ -455,23 +520,23 @@ export default function ITDataTable<T extends Record<string, unknown>>({
455
520
  isPinned && "sticky",
456
521
  isStickyHeader && "sticky top-0 bg-secondary-50",
457
522
  isPinned ? "z-[15]" : isStickyHeader && "z-10",
458
- isPinned && (col.pinned === "left" ? "it-table-pinned-left" : "it-table-pinned-right")
523
+ isPinned && (col.pinned === "left" ? "it-table-pinned-left" : "it-table-pinned-right"),
524
+ headerCellSlot.className,
525
+ col.headerClassName
459
526
  )}
460
- style={
461
- col.minWidth != null || isStickyHeader || isPinned
462
- ? {
463
- ...(col.minWidth != null ? { minWidth: `${col.minWidth}px` } : {}),
464
- ...(isStickyHeader || isPinned
465
- ? { backgroundColor: "var(--color-table-headerBg, #f8fafc)" }
466
- : {}),
467
- ...(isPinned
468
- ? pin.side === "left"
469
- ? { left: pin.offset }
470
- : { right: pin.offset }
471
- : {}),
472
- }
473
- : undefined
474
- }
527
+ style={{
528
+ ...(col.minWidth != null ? { minWidth: `${col.minWidth}px` } : {}),
529
+ ...(isStickyHeader || isPinned
530
+ ? { backgroundColor: "var(--color-table-headerBg, #f8fafc)" }
531
+ : {}),
532
+ ...(isPinned
533
+ ? pin.side === "left"
534
+ ? { left: pin.offset }
535
+ : { right: pin.offset }
536
+ : {}),
537
+ ...headerCellSlot.style,
538
+ ...col.headerStyle,
539
+ }}
475
540
  >
476
541
  <div className={isFixed ? "flex flex-col gap-3" : "flex flex-col gap-3 min-w-[150px]"}>
477
542
  <div className="flex items-center justify-between gap-2">
@@ -516,8 +581,10 @@ export default function ITDataTable<T extends Record<string, unknown>>({
516
581
  className={clsx(
517
582
  tableRow,
518
583
  variant === "striped" && absoluteIndex % 2 === 0 && "bg-secondary-50/40 dark:bg-slate-800/20",
519
- onRowClick && !isLoading && tableRowClickable
584
+ onRowClick && !isLoading && tableRowClickable,
585
+ rowSlot.className
520
586
  )}
587
+ style={rowSlot.style}
521
588
  tabIndex={onRowClick && !isLoading ? 0 : undefined}
522
589
  onClick={onRowClick && !isLoading ? handleRowClick(row) : undefined}
523
590
  onKeyDown={onRowClick && !isLoading ? handleRowKeyDown(row) : undefined}
@@ -539,20 +606,20 @@ export default function ITDataTable<T extends Record<string, unknown>>({
539
606
  col.align && tableAlignClasses[col.align],
540
607
  isPinned && "sticky bg-[var(--color-table-rowBg,white)] group-hover:bg-[var(--color-table-rowHover,var(--color-secondary-50))]",
541
608
  isPinned && (col.pinned === "left" ? "it-table-pinned-left" : "it-table-pinned-right"),
542
- isPinned && "z-[1]"
609
+ isPinned && "z-[1]",
610
+ cellSlot.className,
611
+ col.cellClassName
543
612
  )}
544
- style={
545
- col.minWidth != null || isPinned
546
- ? {
547
- ...(col.minWidth != null ? { minWidth: `${col.minWidth}px` } : {}),
548
- ...(isPinned
549
- ? pin.side === "left"
550
- ? { left: pin.offset }
551
- : { right: pin.offset }
552
- : {}),
553
- }
554
- : undefined
555
- }
613
+ style={{
614
+ ...(col.minWidth != null ? { minWidth: `${col.minWidth}px` } : {}),
615
+ ...(isPinned
616
+ ? pin.side === "left"
617
+ ? { left: pin.offset }
618
+ : { right: pin.offset }
619
+ : {}),
620
+ ...cellSlot.style,
621
+ ...col.cellStyle,
622
+ }}
556
623
  title={truncateTitle}
557
624
  >
558
625
  {col.type === "actions" ? (
@@ -593,10 +660,20 @@ export default function ITDataTable<T extends Record<string, unknown>>({
593
660
  );
594
661
 
595
662
  return (
596
- <div ref={rootRef} className={clsx("space-y-4 w-full relative", containerClassName)}>
597
- <div className={tableContainer} style={{ backgroundColor: 'var(--color-table-rowBg, #ffffff)' }}>
663
+ <div
664
+ ref={rootRef}
665
+ className={clsx("space-y-4 w-full relative", containerSlot.className)}
666
+ style={{ ...colorVars, ...containerSlot.style }}
667
+ >
668
+ <div
669
+ className={clsx(tableContainer, cardSlot.className)}
670
+ style={{ backgroundColor: 'var(--color-table-rowBg, #ffffff)', ...cardSlot.style }}
671
+ >
598
672
  {title && (
599
- <div className="px-6 py-5 flex items-center justify-between" style={{ backgroundColor: 'var(--color-table-rowBg, #ffffff)' }}>
673
+ <div
674
+ className={clsx("px-6 py-5 flex items-center justify-between", toolbarSlot.className)}
675
+ style={{ backgroundColor: 'var(--color-table-rowBg, #ffffff)', ...toolbarSlot.style }}
676
+ >
600
677
  <ITText as="h2" className="text-xl font-bold text-secondary-900 leading-tight">{title}</ITText>
601
678
  <div className="flex items-center gap-3">
602
679
  {isLoading && (
@@ -604,32 +681,14 @@ export default function ITDataTable<T extends Record<string, unknown>>({
604
681
  {loadingIndicator || <FaSpinner className="animate-spin text-primary-500 text-xl" />}
605
682
  </div>
606
683
  )}
607
- <div className="flex items-center bg-slate-100 dark:bg-slate-800/80 rounded-lg p-0.5 border border-slate-200 dark:border-slate-700/50">
608
- <button onClick={() => setViewMode("table")} disabled={forcedCards} className={segCtrlClass("table")}>
609
- <FaTable size={11} />
610
- Table
611
- </button>
612
- <button onClick={() => setViewMode("cards")} className={segCtrlClass("cards")}>
613
- <FaThLarge size={11} />
614
- Cards
615
- </button>
616
- </div>
684
+ {showViewToggle && viewToggle}
617
685
  </div>
618
686
  </div>
619
687
  )}
620
688
 
621
- {!title && (
622
- <div className="flex justify-end px-4 pt-3">
623
- <div className="flex items-center bg-slate-100 dark:bg-slate-800/80 rounded-lg p-0.5 border border-slate-200 dark:border-slate-700/50">
624
- <button onClick={() => setViewMode("table")} disabled={forcedCards} className={segCtrlClass("table")}>
625
- <FaTable size={11} />
626
- Table
627
- </button>
628
- <button onClick={() => setViewMode("cards")} className={segCtrlClass("cards")}>
629
- <FaThLarge size={11} />
630
- Cards
631
- </button>
632
- </div>
689
+ {!title && showViewToggle && (
690
+ <div className={clsx("flex justify-end px-4 pt-3", toolbarSlot.className)} style={toolbarSlot.style}>
691
+ {viewToggle}
633
692
  </div>
634
693
  )}
635
694
 
@@ -648,7 +707,7 @@ export default function ITDataTable<T extends Record<string, unknown>>({
648
707
  )}
649
708
 
650
709
  {effectiveView === "cards" ? (
651
- <div className="p-4 space-y-3 max-h-[400px] overflow-y-auto">
710
+ <div className={clsx("p-4 space-y-3 max-h-[400px] overflow-y-auto", cardsSlot.className)} style={cardsSlot.style}>
652
711
  {data.length > 0 ? (
653
712
  data.map((row, i) => (
654
713
  <div
@@ -685,7 +744,10 @@ export default function ITDataTable<T extends Record<string, unknown>>({
685
744
  </div>
686
745
  </div>
687
746
 
688
- <div className="rounded-b-xl px-6 py-4" style={{ backgroundColor: 'var(--color-table-rowBg, #ffffff)' }}>
747
+ <div
748
+ className={clsx("rounded-b-xl px-6 py-4", footerSlot.className)}
749
+ style={{ backgroundColor: 'var(--color-table-rowBg, #ffffff)', ...footerSlot.style }}
750
+ >
689
751
  <ITPagination
690
752
  currentPage={currentPage}
691
753
  totalPages={computedTotalPages}
package/src/index.ts CHANGED
@@ -119,7 +119,7 @@ import type { ITTripleFilterProps, ITTripleFilterOption } from "./components/org
119
119
  import type { ITToastProps } from "./components/organisms/toast/toast.props";
120
120
  import type { ITWysiwygProps } from "./components/molecules/wysiwyg/wysiwyg.props";
121
121
  import type { FieldConfig, FieldConfigV2 } from "./types/field.types";
122
- import type { ColumnFilters, ColumnFilterValue, TableDensity } from "./types/table.types";
122
+ import type { ColumnFilters, ColumnFilterValue, TableColors, TableDensity, TableDesign, TableDesignProps, TableDesignSlot } from "./types/table.types";
123
123
 
124
124
  // UTILS
125
125
  import ITDropfile, { UploadStatus, FileTypeEnum } from "./components/organisms/dropfile/dropfile";
@@ -185,7 +185,7 @@ export {
185
185
  ITSegmentedControlProps, ITSidebarProps, ITSkeletonProps, ITSlideToggleProps, ITSliderProps,
186
186
  ITStackProps, ITStatCardProps, ITStepperProps, ITLineBarChartProps, ITLineBarDatum,
187
187
  ITTableProps, ITTabsProps, ITTabItem, ITTextProps, ITTextareaProps,
188
- TableDensity,
188
+ TableColors, TableDensity, TableDesign, TableDesignProps, TableDesignSlot,
189
189
  ITTripleFilterProps, ITTripleFilterOption, ITThemeConfig, ITThemeProviderProps, ITThemePalette, ITThemeAppearance,
190
190
  ITTimePickerProps, ITToastProps,
191
191
  ITWysiwygProps,
@@ -358,6 +358,9 @@ export const TableShowcase = () => {
358
358
  '// ITSearchSelect typeahead (server-side search opcional vía catalogOptions.onSearch)\n{ key: "role", label: "Role", type: "string", filter: "search", catalogOptions: { data: roles } }',
359
359
  '// ITDatePicker fecha única\n{ key: "lastLogin", label: "Last login", type: "date", filter: "date", dateFilterOptions: { maxDate: new Date() } }',
360
360
  '// ITDatePicker rango\n{ key: "hiredAt", label: "Hired", type: "date", filter: "date-range", dateFilterOptions: { maxDate: new Date() } }',
361
+ '// Diseño por instancia: colors pisa el tema; design estiliza cada región\n<ITTable columns={cols} data={rows} showViewToggle={false}\n colors={{ headerBg: "#0f172a", headerText: "#e2e8f0", rowHover: "#e0f2fe" }}\n design={{ card: { className: "rounded-2xl border-2 border-primary-200" }, headerCell: { className: "py-4" }, cell: { className: "font-semibold" } }} />',
362
+ '// Atajos planos equivalentes (ganan sobre design)*\n<ITTable columns={cols} data={rows} cardClassName="rounded-2xl" headerCellStyle={{ letterSpacing: "0.1em" }} rowClassName="border-b border-slate-100" cellClassName="font-semibold" />',
363
+ '// Diseño por columna\n{ key: "sales", label: "Sales", type: "number", headerClassName: "text-right", cellClassName: "text-right", cellStyle: { fontVariantNumeric: "tabular-nums" } }',
361
364
  ],
362
365
  props: [
363
366
  { name: "columns", type: "Column<T>[]", description: "Definiciones de columna: key, label, type, sortable, filter, render, actions." },
@@ -382,11 +385,24 @@ export const TableShowcase = () => {
382
385
  { name: "rowHeight", type: "number", default: "getRowHeight(size, density)", description: "Alto de fila asumido (px). Size-aware: baseline size=\"md\" = 33/45/53 según densidad; size lo desplaza por el line box (sm 16 / md 20 / lg 28 px)." },
383
386
  { name: "overscan", type: "number", default: "5", description: "Filas extra renderizadas arriba/abajo del viewport." },
384
387
  { name: "stickyHeader", type: "boolean", default: "false", description: "Header sticky mientras scrollea el cuerpo virtualizado." },
388
+ { name: "showViewToggle", type: "boolean", default: "true", description: "Muestra/oculta el switch Table|Cards. Ocultarlo no desactiva defaultView ni autoCardBreakpoint." },
389
+ { name: "colors", type: "TableColors", description: "Colores por instancia (headerBg, headerText, rowBg, rowText, rowHover); pisan el tema global solo en esta tabla." },
390
+ { name: "design", type: "TableDesign", description: "Estilo por región: container, card, toolbar, header, headerCell, row, cell, cards, footer. Cada uno con className y style." },
391
+ { name: "cardClassName / cardStyle", type: "string / CSSProperties", description: "Superficie tipo card (borde, radio, sombra, fondo)." },
392
+ { name: "toolbarClassName / toolbarStyle", type: "string / CSSProperties", description: "Barra con el título y el switch Table/Cards." },
393
+ { name: "headerClassName / headerStyle", type: "string / CSSProperties", description: "Fila de títulos (<thead> <tr>)." },
394
+ { name: "headerCellClassName / headerCellStyle", type: "string / CSSProperties", description: "Cada celda de encabezado (<th>)." },
395
+ { name: "rowClassName / rowStyle", type: "string / CSSProperties", description: "Cada fila del cuerpo (<tr>)." },
396
+ { name: "cellClassName / cellStyle", type: "string / CSSProperties", description: "Cada celda del cuerpo (<td>)." },
397
+ { name: "cardsClassName / cardsStyle", type: "string / CSSProperties", description: "Contenedor de la vista de cards." },
398
+ { name: "footerClassName / footerStyle", type: "string / CSSProperties", description: "Franja de paginación." },
399
+ { name: "containerStyle", type: "CSSProperties", description: "Estilos inline del contenedor externo." },
385
400
  ],
386
401
  notes: [
387
402
  "onRowClick funciona en ambas vistas (tabla y cards).",
388
403
  "Cuando se omite no se agrega cursor-pointer, foco, tabIndex ni role.",
389
- "Column acepta width (number=px | string CSS), minWidth (px), align (left/center/right) y truncate (ellipsis + title nativo).",
404
+ "Column acepta width (number=px | string CSS), minWidth (px), align (left/center/right) y truncate (ellipsis + title nativo). Diseño por columna: headerClassName, headerStyle, cellClassName y cellStyle (className aplica a th y td).",
405
+ "Diseño por instancia: design (o los atajos planos <slot>ClassName/<slot>Style) estiliza container, card, toolbar, header, headerCell, row, cell, cards y footer; el atajo plano gana sobre design. colors instala --color-table-* locales y pisa el tema global. showViewToggle controla el switch Table|Cards y autoCardBreakpoint (px) fuerza la vista cards bajo ese ancho.",
390
406
  "Columna fija: Column acepta pinned: \"left\" | \"right\" (opt-in; omitir = sin cambios). Los offsets sticky acumulan el width en px de cada columna pinneada previa del mismo lado; una columna sin width en px aporta 0px, así que declara width en px en todas las columnas pinneadas de un lado. La celda pinneada pinta fondo opaco (rowBg + rowHover) y un separador de 1px en el borde congelado. Una columna pinneada por lado es el contrato probado. Limitación: con variant=\"striped\" el fondo opaco tapa el tinte de la franja de esa columna.",
391
407
  "Anti scroll horizontal: layout=\"fixed\" + width por columna + truncate + density.",
392
408
  "Virtualización: solo vista tabla; rinde con page size grande; filas de alto uniforme; el scroll no se reinicia al cambiar filtros.",
@@ -557,6 +573,58 @@ export const TableShowcase = () => {
557
573
  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.
558
574
  </p>
559
575
  </div>
576
+
577
+ <div>
578
+ <h4 className="text-sm font-bold text-slate-700 dark:text-slate-200 mb-3">Design overrides — colors + design slots, sin switch</h4>
579
+ <div className="border border-slate-200 dark:border-slate-700 rounded-xl overflow-hidden">
580
+ <ITTable
581
+ columns={columns}
582
+ data={TABLE_DATA.slice(0, 5)}
583
+ title="Custom design"
584
+ defaultItemsPerPage={5}
585
+ showViewToggle={false}
586
+ colors={{
587
+ headerBg: "#0f172a",
588
+ headerText: "#e2e8f0",
589
+ rowBg: "#ffffff",
590
+ rowText: "#0f172a",
591
+ rowHover: "#e0f2fe",
592
+ }}
593
+ design={{
594
+ card: { className: "rounded-2xl border-2 border-primary-200 shadow-lg" },
595
+ header: { className: "tracking-widest" },
596
+ headerCell: { className: "py-4" },
597
+ row: { className: "border-b border-slate-100" },
598
+ cell: { className: "font-semibold" },
599
+ }}
600
+ />
601
+ </div>
602
+ <p className="mt-2 text-xs text-slate-500 dark:text-slate-400">
603
+ <code>colors</code> pisa el tema global solo para esta instancia; <code>design</code> (o los atajos <code>cardClassName</code>, <code>headerCellStyle</code>, <code>rowClassName</code>, <code>cellStyle</code>…) estiliza cada región. <code>showViewToggle=&#123;false&#125;</code> oculta el switch Table/Cards.
604
+ </p>
605
+ </div>
606
+
607
+ <div>
608
+ <h4 className="text-sm font-bold text-slate-700 dark:text-slate-200 mb-3">Per-column design — header y celda por columna</h4>
609
+ <div className="border border-slate-200 dark:border-slate-700 rounded-xl overflow-hidden">
610
+ <ITTable
611
+ columns={columns.map((col: any) =>
612
+ col.key === "sales"
613
+ ? { ...col, headerClassName: "text-right", cellClassName: "text-right", cellStyle: { fontVariantNumeric: "tabular-nums" } }
614
+ : col.key === "name"
615
+ ? { ...col, headerStyle: { letterSpacing: "0.1em" } }
616
+ : col
617
+ )}
618
+ data={TABLE_DATA.slice(0, 5)}
619
+ title="Per-column"
620
+ defaultItemsPerPage={5}
621
+ showViewToggle={false}
622
+ />
623
+ </div>
624
+ <p className="mt-2 text-xs text-slate-500 dark:text-slate-400">
625
+ Cada <code>Column</code> acepta <code>headerClassName</code>, <code>headerStyle</code>, <code>cellClassName</code> y <code>cellStyle</code>, aplicados solo a esa columna.
626
+ </p>
627
+ </div>
560
628
  </div>
561
629
  }
562
630
  />
@@ -950,11 +1018,24 @@ export const DataTableShowcase = () => {
950
1018
  { name: "rowHeight", type: "number", default: "getRowHeight(size, density)", description: "Alto de fila asumido (px). Size-aware: baseline size=\"md\" = 33/45/53 según densidad; size lo desplaza por el line box (sm 16 / md 20 / lg 28 px)." },
951
1019
  { name: "overscan", type: "number", default: "5", description: "Filas extra renderizadas arriba/abajo del viewport." },
952
1020
  { name: "stickyHeader", type: "boolean", default: "false", description: "Header sticky mientras scrollea el cuerpo virtualizado." },
1021
+ { name: "showViewToggle", type: "boolean", default: "true", description: "Muestra/oculta el switch Table|Cards. Ocultarlo no desactiva defaultView ni autoCardBreakpoint." },
1022
+ { name: "colors", type: "TableColors", description: "Colores por instancia (headerBg, headerText, rowBg, rowText, rowHover); pisan el tema global solo en esta tabla." },
1023
+ { name: "design", type: "TableDesign", description: "Estilo por región: container, card, toolbar, header, headerCell, row, cell, cards, footer. Cada uno con className y style." },
1024
+ { name: "cardClassName / cardStyle", type: "string / CSSProperties", description: "Superficie tipo card (borde, radio, sombra, fondo)." },
1025
+ { name: "toolbarClassName / toolbarStyle", type: "string / CSSProperties", description: "Barra con el título y el switch Table/Cards." },
1026
+ { name: "headerClassName / headerStyle", type: "string / CSSProperties", description: "Fila de títulos (<thead> <tr>)." },
1027
+ { name: "headerCellClassName / headerCellStyle", type: "string / CSSProperties", description: "Cada celda de encabezado (<th>)." },
1028
+ { name: "rowClassName / rowStyle", type: "string / CSSProperties", description: "Cada fila del cuerpo (<tr>)." },
1029
+ { name: "cellClassName / cellStyle", type: "string / CSSProperties", description: "Cada celda del cuerpo (<td>)." },
1030
+ { name: "cardsClassName / cardsStyle", type: "string / CSSProperties", description: "Contenedor de la vista de cards." },
1031
+ { name: "footerClassName / footerStyle", type: "string / CSSProperties", description: "Franja de paginación." },
1032
+ { name: "containerStyle", type: "CSSProperties", description: "Estilos inline del contenedor externo." },
953
1033
  ],
954
1034
  notes: [
955
1035
  "onRowClick funciona en ambas vistas (tabla y cards).",
956
1036
  "Interacciones que inician en botones, links o inputs no disparan onRowClick.",
957
- "Column acepta width (number=px | string CSS), minWidth (px), align (left/center/right) y truncate (ellipsis + title nativo).",
1037
+ "Column acepta width (number=px | string CSS), minWidth (px), align (left/center/right) y truncate (ellipsis + title nativo). Diseño por columna: headerClassName, headerStyle, cellClassName y cellStyle (className aplica a th y td).",
1038
+ "Diseño por instancia: design (o los atajos planos <slot>ClassName/<slot>Style) estiliza container, card, toolbar, header, headerCell, row, cell, cards y footer; el atajo plano gana sobre design. colors instala --color-table-* locales y pisa el tema global. showViewToggle controla el switch Table|Cards y autoCardBreakpoint (px) fuerza la vista cards bajo ese ancho.",
958
1039
  "Columna fija: Column acepta pinned: \"left\" | \"right\" (opt-in; omitir = sin cambios). Los offsets sticky acumulan el width en px de cada columna pinneada previa del mismo lado; una columna sin width en px aporta 0px, así que declara width en px en todas las columnas pinneadas de un lado. La celda pinneada pinta fondo opaco (rowBg + rowHover) y un separador de 1px en el borde congelado. Una columna pinneada por lado es el contrato probado. Limitación: con variant=\"striped\" el fondo opaco tapa el tinte de la franja de esa columna.",
959
1040
  "Anti scroll horizontal: layout=\"fixed\" + width por columna + truncate + density.",
960
1041
  "Virtualización: solo vista tabla; rinde con page size grande; filas de alto uniforme; el scroll no se reinicia al cambiar filtros.",
@@ -1023,6 +1104,30 @@ export const DataTableShowcase = () => {
1023
1104
  </div>
1024
1105
  </div>
1025
1106
 
1107
+ <div>
1108
+ <h4 className="text-sm font-bold text-slate-700 dark:text-slate-200 mb-3">Design overrides — colors + design slots, sin switch</h4>
1109
+ <div className="border border-slate-200 dark:border-slate-700 rounded-xl overflow-hidden">
1110
+ <ITDataTable
1111
+ columns={userColumns}
1112
+ fetchData={fetchData}
1113
+ title="Custom design"
1114
+ defaultItemsPerPage={4}
1115
+ debounceMs={0}
1116
+ showViewToggle={false}
1117
+ colors={{ headerBg: "#0f172a", headerText: "#e2e8f0", rowHover: "#e0f2fe" }}
1118
+ design={{
1119
+ card: { className: "rounded-2xl border-2 border-primary-200 shadow-lg" },
1120
+ headerCell: { className: "py-4" },
1121
+ row: { className: "border-b border-slate-100" },
1122
+ cell: { className: "font-semibold" },
1123
+ }}
1124
+ />
1125
+ </div>
1126
+ <p className="mt-2 text-xs text-slate-500 dark:text-slate-400">
1127
+ <code>colors</code> pisa el tema global solo para esta instancia; <code>design</code> (o <code>cardClassName</code>, <code>headerCellStyle</code>, <code>rowClassName</code>, <code>cellStyle</code>…) estiliza cada región. <code>showViewToggle=&#123;false&#125;</code> oculta el switch.
1128
+ </p>
1129
+ </div>
1130
+
1026
1131
  <div>
1027
1132
  <h4 className="text-sm font-bold text-slate-700 dark:text-slate-200 mb-3">Pinned columns — left (ID) &amp; right (Active)</h4>
1028
1133
  <div className="border border-slate-200 dark:border-slate-700 rounded-xl overflow-hidden" style={{ maxWidth: 520 }}>
@@ -1,5 +1,115 @@
1
+ import type { CSSProperties } from "react";
2
+
1
3
  export type TableVariants = "default" | "striped" | "bordered";
2
4
 
5
+ /**
6
+ * A single, optional style slot for one region of a table. Both fields are
7
+ * merged *after* the component's built-in values.
8
+ */
9
+ export interface TableDesignSlot {
10
+ /**
11
+ * Extra CSS classes merged after the built-in ones. Note that Tailwind
12
+ * resolves conflicts by utility order (not class order), so use inline
13
+ * `style` (or `!` modifiers) when a value must always win.
14
+ */
15
+ className?: string;
16
+ /** Inline CSS merged after the built-in values; wins over classes unless the global theme forces a value with `!important`. */
17
+ style?: CSSProperties;
18
+ }
19
+
20
+ /**
21
+ * Per-instance visual overrides for a table. Every region is optional and, when
22
+ * present, is applied on top of the built-in styles. The global
23
+ * `ITThemeProvider` colors (`--color-table-*`) still apply underneath, so use
24
+ * `TableColors` for this-instance color changes instead of `style`.
25
+ */
26
+ export interface TableDesign {
27
+ /** Outermost wrapper (`space-y-4 w-full`). Prefer `containerClassName` for width/spacing. */
28
+ container?: TableDesignSlot;
29
+ /** The rounded card surface that wraps the toolbar, table and footer (border, radius, shadow, background). */
30
+ card?: TableDesignSlot;
31
+ /** Toolbar strip holding the title and/or the Table|Cards toggle. */
32
+ toolbar?: TableDesignSlot;
33
+ /** The `<thead>` row that carries the column titles. */
34
+ header?: TableDesignSlot;
35
+ /** Every `<th>` header cell. */
36
+ headerCell?: TableDesignSlot;
37
+ /** Every body `<tr>`. */
38
+ row?: TableDesignSlot;
39
+ /** Every body `<td>`. */
40
+ cell?: TableDesignSlot;
41
+ /** Scroll wrapper of the cards view (the responsive card list). */
42
+ cards?: TableDesignSlot;
43
+ /** Pagination footer strip. */
44
+ footer?: TableDesignSlot;
45
+ }
46
+
47
+ /**
48
+ * Per-instance color overrides. They are written as local `--color-table-*`
49
+ * variables on the table wrapper, so they beat the global theme for this
50
+ * instance only, without touching `ITThemeProvider`.
51
+ */
52
+ export interface TableColors {
53
+ /** Header row background. Overrides the theme's `table.headerBg` for this instance. */
54
+ headerBg?: string;
55
+ /** Header text color. Overrides the theme's `table.headerText` for this instance. */
56
+ headerText?: string;
57
+ /** Body row background. Overrides the theme's `table.rowBg` for this instance. */
58
+ rowBg?: string;
59
+ /** Body text color. Overrides the theme's `table.rowText` for this instance. */
60
+ rowText?: string;
61
+ /** Body row hover background. Overrides the theme's `table.rowHover` for this instance. */
62
+ rowHover?: string;
63
+ }
64
+
65
+ /**
66
+ * Shared design props for ITTable and ITDataTable. `design` exposes every
67
+ * region as an object; the flat `*ClassName` / `*Style` shortcuts are merged
68
+ * after it, so the flat prop wins when both are provided.
69
+ */
70
+ export interface TableDesignProps {
71
+ /** Whether to render the Table|Cards view switcher in the toolbar. Hiding it still honors `defaultView` and `autoCardBreakpoint`. @default true */
72
+ showViewToggle?: boolean;
73
+ /** Per-region visual overrides (classes + inline styles). */
74
+ design?: TableDesign;
75
+ /** Per-instance color overrides for header and rows, beating the global theme. */
76
+ colors?: TableColors;
77
+ /** Inline styles for the outermost wrapper. */
78
+ containerStyle?: CSSProperties;
79
+ /** Extra classes for the card surface (the rounded container). */
80
+ cardClassName?: string;
81
+ /** Inline styles for the card surface. */
82
+ cardStyle?: CSSProperties;
83
+ /** Extra classes for the toolbar strip (title + view toggle). */
84
+ toolbarClassName?: string;
85
+ /** Inline styles for the toolbar strip. */
86
+ toolbarStyle?: CSSProperties;
87
+ /** Extra classes for the header row. */
88
+ headerClassName?: string;
89
+ /** Inline styles for the header row. */
90
+ headerStyle?: CSSProperties;
91
+ /** Extra classes for every header cell. */
92
+ headerCellClassName?: string;
93
+ /** Inline styles for every header cell. */
94
+ headerCellStyle?: CSSProperties;
95
+ /** Extra classes for every body row. */
96
+ rowClassName?: string;
97
+ /** Inline styles for every body row. */
98
+ rowStyle?: CSSProperties;
99
+ /** Extra classes for every body cell. */
100
+ cellClassName?: string;
101
+ /** Inline styles for every body cell. */
102
+ cellStyle?: CSSProperties;
103
+ /** Extra classes for the cards-view scroll wrapper. */
104
+ cardsClassName?: string;
105
+ /** Inline styles for the cards-view scroll wrapper. */
106
+ cardsStyle?: CSSProperties;
107
+ /** Extra classes for the pagination footer strip. */
108
+ footerClassName?: string;
109
+ /** Inline styles for the pagination footer strip. */
110
+ footerStyle?: CSSProperties;
111
+ }
112
+
3
113
  /**
4
114
  * Value stored for a single column filter.
5
115
  *