@axzydev/axzy_ui_system 1.3.4 → 1.3.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/README.md +1 -1
- package/dist/index.cjs +754 -613
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +148 -5
- package/dist/index.d.ts +148 -5
- package/dist/index.js +628 -488
- package/dist/index.js.map +1 -1
- package/package.json +9 -3
- package/src/App.tsx +6 -0
- package/src/components/atoms/input/input.props.ts +5 -0
- package/src/components/atoms/input/input.tsx +24 -17
- package/src/components/atoms/input-number/input-number.doc.mdx +74 -0
- package/src/components/atoms/input-number/input-number.props.ts +66 -0
- package/src/components/atoms/input-number/input-number.stories.tsx +111 -0
- package/src/components/atoms/input-number/input-number.tsx +86 -0
- package/src/components/molecules/table/table.doc.mdx +12 -1
- package/src/components/molecules/table/table.props.ts +17 -0
- package/src/components/molecules/table/table.stories.tsx +45 -0
- package/src/components/molecules/table/table.tsx +37 -8
- package/src/components/organisms/data-table/ITDataTable.doc.mdx +14 -1
- package/src/components/organisms/data-table/ITDataTable.stories.tsx +25 -0
- package/src/components/organisms/data-table/dataTable.tsx +37 -8
- package/src/components/organisms/sidebar/sidebar.doc.mdx +50 -12
- package/src/components/organisms/sidebar/sidebar.props.ts +28 -2
- package/src/components/organisms/sidebar/sidebar.stories.tsx +61 -1
- package/src/components/organisms/sidebar/sidebar.tsx +111 -57
- package/src/index.css +10 -0
- package/src/index.ts +6 -3
- package/src/sandbox/navigation.ts +2 -0
- package/src/showcases/DataShowcases.tsx +61 -0
- package/src/showcases/FormShowcases.tsx +287 -1
- package/src/showcases/StructureShowcases.tsx +34 -6
- package/src/utils/table.utils.ts +83 -0
|
@@ -1,8 +1,30 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from "react";
|
|
2
2
|
import { FaChevronDown } from "react-icons/fa";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ITNavigationItem,
|
|
5
|
+
ITNavigationSubItem,
|
|
6
|
+
ITNavigationSubItemEntry,
|
|
7
|
+
ITNavigationSubItemGroup,
|
|
8
|
+
ITSidebarProps,
|
|
9
|
+
} from "./sidebar.props";
|
|
4
10
|
import ITText from "@/components/atoms/text/text";
|
|
5
11
|
|
|
12
|
+
/** Narrows a submenu entry to a titled group. */
|
|
13
|
+
function isSubItemGroup(
|
|
14
|
+
entry: ITNavigationSubItemEntry,
|
|
15
|
+
): entry is ITNavigationSubItemGroup {
|
|
16
|
+
return "items" in entry;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Flattens a parent's entries (plain items + groups) into a single list. */
|
|
20
|
+
function flattenSubItems(
|
|
21
|
+
entries: ITNavigationSubItemEntry[],
|
|
22
|
+
): ITNavigationSubItem[] {
|
|
23
|
+
return entries.flatMap((entry) =>
|
|
24
|
+
isSubItemGroup(entry) ? entry.items : [entry],
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
6
28
|
/**
|
|
7
29
|
* Vertical navigation sidebar with submenu expand/collapse, hover tooltips in collapsed mode,
|
|
8
30
|
* and glassmorphism styling. The collapsed rail expands automatically on pointer hover — there
|
|
@@ -76,7 +98,7 @@ export default function ITSidebar({
|
|
|
76
98
|
useEffect(() => {
|
|
77
99
|
const activeParents = new Set<string>();
|
|
78
100
|
navigationItems.forEach(item => {
|
|
79
|
-
if (item.subitems && item.subitems.some(sub => sub.isActive)) {
|
|
101
|
+
if (item.subitems && flattenSubItems(item.subitems).some(sub => sub.isActive)) {
|
|
80
102
|
activeParents.add(item.id);
|
|
81
103
|
}
|
|
82
104
|
});
|
|
@@ -115,6 +137,86 @@ export default function ITSidebar({
|
|
|
115
137
|
const isSidebarCollapsed = visibleOnMobile ? false : (!isHovering && isCollapsed);
|
|
116
138
|
const sidebarWidth = isSidebarCollapsed ? "w-[88px]" : "w-[280px]";
|
|
117
139
|
|
|
140
|
+
const renderSubItem = (subitem: ITNavigationSubItem) => (
|
|
141
|
+
<li key={subitem.id} className="relative">
|
|
142
|
+
<button
|
|
143
|
+
aria-current={subitem.isActive ? "page" : undefined}
|
|
144
|
+
onClick={() => {
|
|
145
|
+
if (subitem.action) subitem.action();
|
|
146
|
+
if (onSubItemClick) onSubItemClick(subitem);
|
|
147
|
+
}}
|
|
148
|
+
className={`flex items-center gap-2 w-full text-left px-3 py-1.5 rounded-xl transition-all duration-300`}
|
|
149
|
+
style={{
|
|
150
|
+
color: subitem.isActive ? "var(--it-sidebar-active-color, var(--color-secondary-900))" : "var(--it-sidebar-label-color, var(--color-secondary-600))",
|
|
151
|
+
backgroundColor: subitem.isActive ? "var(--it-sidebar-active-bg, var(--color-secondary-50))" : 'transparent',
|
|
152
|
+
fontSize: '0.78rem',
|
|
153
|
+
fontWeight: subitem.isActive ? 600 : 500,
|
|
154
|
+
letterSpacing: '0.01em',
|
|
155
|
+
marginLeft: subitemConnector === '|' ? '-1px' : '0',
|
|
156
|
+
}}
|
|
157
|
+
onMouseEnter={(e) => {
|
|
158
|
+
if (!subitem.isActive) {
|
|
159
|
+
e.currentTarget.style.backgroundColor = "var(--it-sidebar-hover-bg, var(--color-secondary-100))";
|
|
160
|
+
e.currentTarget.style.transform = 'translateX(3px)';
|
|
161
|
+
}
|
|
162
|
+
}}
|
|
163
|
+
onMouseLeave={(e) => {
|
|
164
|
+
if (!subitem.isActive) {
|
|
165
|
+
e.currentTarget.style.backgroundColor = 'transparent';
|
|
166
|
+
e.currentTarget.style.transform = 'translateX(0)';
|
|
167
|
+
}
|
|
168
|
+
}}
|
|
169
|
+
>
|
|
170
|
+
{subitem.isActive && subitemConnector === '|' && (
|
|
171
|
+
<div
|
|
172
|
+
className="absolute left-0 top-1/3 bottom-1/3 w-[2.5px] rounded-r-full transition-all"
|
|
173
|
+
style={{
|
|
174
|
+
backgroundColor: "var(--it-sidebar-active-icon, var(--color-primary-500))",
|
|
175
|
+
boxShadow: "0 0 6px color-mix(in srgb, var(--it-sidebar-active-icon, var(--color-primary-500)) 25%, transparent)",
|
|
176
|
+
}}
|
|
177
|
+
/>
|
|
178
|
+
)}
|
|
179
|
+
{subitemConnector === 'dot' && (
|
|
180
|
+
<span
|
|
181
|
+
className={`w-1.5 h-1.5 rounded-full flex-shrink-0 transition-all duration-300 ${subitem.isActive ? 'scale-125' : ''}`}
|
|
182
|
+
style={{
|
|
183
|
+
backgroundColor: subitem.isActive
|
|
184
|
+
? "var(--it-sidebar-active-icon, var(--color-primary-500))"
|
|
185
|
+
: "var(--it-sidebar-icon-color, var(--color-secondary-400))"
|
|
186
|
+
}}
|
|
187
|
+
/>
|
|
188
|
+
)}
|
|
189
|
+
<ITText as="span" className="truncate">{subitem.label}</ITText>
|
|
190
|
+
</button>
|
|
191
|
+
</li>
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const renderSubItemGroup = (group: ITNavigationSubItemGroup, itemId: string) => {
|
|
195
|
+
const headingId = `it-sidebar-subgroup-${itemId}-${group.id}`;
|
|
196
|
+
return (
|
|
197
|
+
<li key={group.id} className="relative">
|
|
198
|
+
<div id={headingId} className="px-3 pt-2.5 pb-1 mt-1">
|
|
199
|
+
<ITText
|
|
200
|
+
as="span"
|
|
201
|
+
className="block"
|
|
202
|
+
style={{
|
|
203
|
+
color: "var(--it-sidebar-icon-color, var(--color-secondary-400))",
|
|
204
|
+
fontSize: "0.68rem",
|
|
205
|
+
fontWeight: 700,
|
|
206
|
+
letterSpacing: "0.08em",
|
|
207
|
+
textTransform: "uppercase",
|
|
208
|
+
}}
|
|
209
|
+
>
|
|
210
|
+
{group.label}
|
|
211
|
+
</ITText>
|
|
212
|
+
</div>
|
|
213
|
+
<ul aria-labelledby={headingId} className="flex flex-col gap-0">
|
|
214
|
+
{group.items.map(renderSubItem)}
|
|
215
|
+
</ul>
|
|
216
|
+
</li>
|
|
217
|
+
);
|
|
218
|
+
};
|
|
219
|
+
|
|
118
220
|
return (
|
|
119
221
|
<aside
|
|
120
222
|
ref={sidebarRef}
|
|
@@ -256,7 +358,7 @@ export default function ITSidebar({
|
|
|
256
358
|
|
|
257
359
|
{item.subitems && item.subitems.length > 0 ? (
|
|
258
360
|
<div className="py-2">
|
|
259
|
-
{item.subitems.map((subitem) => (
|
|
361
|
+
{flattenSubItems(item.subitems).map((subitem) => (
|
|
260
362
|
<div
|
|
261
363
|
key={subitem.id}
|
|
262
364
|
className={`px-5 py-2.5 text-sm flex items-center gap-3 transition-colors relative`}
|
|
@@ -283,7 +385,7 @@ export default function ITSidebar({
|
|
|
283
385
|
|
|
284
386
|
{/* Submenu - smooth height/opacity when not collapsed */}
|
|
285
387
|
{!isSidebarCollapsed && item.subitems && item.subitems.length > 0 && (
|
|
286
|
-
<div id={submenuId} role="group" aria-label={item.label} className={`overflow-hidden transition-all duration-400 ease-[cubic-bezier(0.2,0,0,1)] ${expandedItems.has(item.id) ? "max-h-[
|
|
388
|
+
<div id={submenuId} role="group" aria-label={item.label} className={`overflow-hidden transition-all duration-400 ease-[cubic-bezier(0.2,0,0,1)] ${expandedItems.has(item.id) ? "max-h-[1000px] opacity-100 mt-1" : "max-h-0 opacity-0"}`}>
|
|
287
389
|
<ul
|
|
288
390
|
className="ml-4 flex flex-col gap-0 py-0.5"
|
|
289
391
|
style={{
|
|
@@ -292,59 +394,11 @@ export default function ITSidebar({
|
|
|
292
394
|
: 'none'
|
|
293
395
|
}}
|
|
294
396
|
>
|
|
295
|
-
{item.subitems.map((
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
if (subitem.action) subitem.action();
|
|
301
|
-
if (onSubItemClick) onSubItemClick(subitem);
|
|
302
|
-
}}
|
|
303
|
-
className={`flex items-center gap-2 w-full text-left px-3 py-1.5 rounded-xl transition-all duration-300`}
|
|
304
|
-
style={{
|
|
305
|
-
color: subitem.isActive ? "var(--it-sidebar-active-color, var(--color-secondary-900))" : "var(--it-sidebar-label-color, var(--color-secondary-600))",
|
|
306
|
-
backgroundColor: subitem.isActive ? "var(--it-sidebar-active-bg, var(--color-secondary-50))" : 'transparent',
|
|
307
|
-
fontSize: '0.78rem',
|
|
308
|
-
fontWeight: subitem.isActive ? 600 : 500,
|
|
309
|
-
letterSpacing: '0.01em',
|
|
310
|
-
marginLeft: subitemConnector === '|' ? '-1px' : '0',
|
|
311
|
-
}}
|
|
312
|
-
onMouseEnter={(e) => {
|
|
313
|
-
if (!subitem.isActive) {
|
|
314
|
-
e.currentTarget.style.backgroundColor = "var(--it-sidebar-hover-bg, var(--color-secondary-100))";
|
|
315
|
-
e.currentTarget.style.transform = 'translateX(3px)';
|
|
316
|
-
}
|
|
317
|
-
}}
|
|
318
|
-
onMouseLeave={(e) => {
|
|
319
|
-
if (!subitem.isActive) {
|
|
320
|
-
e.currentTarget.style.backgroundColor = 'transparent';
|
|
321
|
-
e.currentTarget.style.transform = 'translateX(0)';
|
|
322
|
-
}
|
|
323
|
-
}}
|
|
324
|
-
>
|
|
325
|
-
{subitem.isActive && subitemConnector === '|' && (
|
|
326
|
-
<div
|
|
327
|
-
className="absolute left-0 top-1/3 bottom-1/3 w-[2.5px] rounded-r-full transition-all"
|
|
328
|
-
style={{
|
|
329
|
-
backgroundColor: "var(--it-sidebar-active-icon, var(--color-primary-500))",
|
|
330
|
-
boxShadow: "0 0 6px color-mix(in srgb, var(--it-sidebar-active-icon, var(--color-primary-500)) 25%, transparent)",
|
|
331
|
-
}}
|
|
332
|
-
/>
|
|
333
|
-
)}
|
|
334
|
-
{subitemConnector === 'dot' && (
|
|
335
|
-
<span
|
|
336
|
-
className={`w-1.5 h-1.5 rounded-full flex-shrink-0 transition-all duration-300 ${subitem.isActive ? 'scale-125' : ''}`}
|
|
337
|
-
style={{
|
|
338
|
-
backgroundColor: subitem.isActive
|
|
339
|
-
? "var(--it-sidebar-active-icon, var(--color-primary-500))"
|
|
340
|
-
: "var(--it-sidebar-icon-color, var(--color-secondary-400))"
|
|
341
|
-
}}
|
|
342
|
-
/>
|
|
343
|
-
)}
|
|
344
|
-
<ITText as="span" className="truncate">{subitem.label}</ITText>
|
|
345
|
-
</button>
|
|
346
|
-
</li>
|
|
347
|
-
))}
|
|
397
|
+
{item.subitems.map((entry) =>
|
|
398
|
+
isSubItemGroup(entry)
|
|
399
|
+
? renderSubItemGroup(entry, item.id)
|
|
400
|
+
: renderSubItem(entry),
|
|
401
|
+
)}
|
|
348
402
|
</ul>
|
|
349
403
|
</div>
|
|
350
404
|
)}
|
package/src/index.css
CHANGED
|
@@ -160,4 +160,14 @@
|
|
|
160
160
|
.it-stepper-slide-in-left {
|
|
161
161
|
animation: it-slide-in-left 320ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
|
|
162
162
|
}
|
|
163
|
+
|
|
164
|
+
/* Pinned (frozen) table columns: 1px edge separator on the frozen side.
|
|
165
|
+
Background + hover live in the component as Tailwind arbitrary utilities so
|
|
166
|
+
the cell matches --color-table-rowBg / --color-table-rowHover. */
|
|
167
|
+
:where(.it-table-pinned-left) {
|
|
168
|
+
box-shadow: inset -1px 0 0 0 var(--color-secondary-200);
|
|
169
|
+
}
|
|
170
|
+
:where(.it-table-pinned-right) {
|
|
171
|
+
box-shadow: inset 1px 0 0 0 var(--color-secondary-200);
|
|
172
|
+
}
|
|
163
173
|
}
|
package/src/index.ts
CHANGED
|
@@ -36,6 +36,7 @@ import ITFormHeader from "./components/molecules/form-header/form-header";
|
|
|
36
36
|
import ITGrid from "./components/atoms/grid/grid";
|
|
37
37
|
import ITImage from "./components/atoms/image/image";
|
|
38
38
|
import ITInput from "./components/atoms/input/input";
|
|
39
|
+
import ITInputNumber from "./components/atoms/input-number/input-number";
|
|
39
40
|
import ITNavbar from "./components/organisms/navbar/navbar";
|
|
40
41
|
import ITPage from "./components/templates/page/page";
|
|
41
42
|
import ITPageHeader from "./components/organisms/page-header/page-header";
|
|
@@ -90,6 +91,7 @@ import type { ITFormHeaderProps } from "./components/molecules/form-header/form-
|
|
|
90
91
|
import type { ITGridProps } from "./components/atoms/grid/grid.props";
|
|
91
92
|
import type { ITImageProps } from "./components/atoms/image/image.props";
|
|
92
93
|
import type { ITInputProps } from "./components/atoms/input/input.props";
|
|
94
|
+
import type { ITInputNumberProps } from "./components/atoms/input-number/input-number.props";
|
|
93
95
|
import type { ITNavbarProps } from "./components/organisms/navbar/navbar.props";
|
|
94
96
|
import type { ITPageProps } from "./components/templates/page/page.props";
|
|
95
97
|
import type { ITPageHeaderProps } from "./components/organisms/page-header/page-header.props";
|
|
@@ -101,7 +103,7 @@ import type { ITSearchSelectProps } from "./components/molecules/search-select/s
|
|
|
101
103
|
import type { ITSelectProps } from "./components/molecules/select/select.props";
|
|
102
104
|
import type { ITSearchTableProps } from "./components/organisms/searchTable/searchTable.props";
|
|
103
105
|
import type { ITSegmentedControlProps } from "./components/atoms/segmented-control/segmented-control.props";
|
|
104
|
-
import type { ITSidebarProps, ITNavigationItem, ITNavigationSubItem } from "./components/organisms/sidebar/sidebar.props";
|
|
106
|
+
import type { ITSidebarProps, ITNavigationItem, ITNavigationSubItem, ITNavigationSubItemEntry, ITNavigationSubItemGroup } from "./components/organisms/sidebar/sidebar.props";
|
|
105
107
|
import type { ITSkeletonProps } from "./components/atoms/skeleton/skeleton.props";
|
|
106
108
|
import type { ITSlideToggleProps } from "./components/atoms/slide/slide.props";
|
|
107
109
|
import type { ITSliderProps } from "./components/atoms/slider/slider.props";
|
|
@@ -145,7 +147,7 @@ export {
|
|
|
145
147
|
ITCard, ITCheckbox, ITChip, ITChipInput, ITConfirmDialog, ITDataTable, ITDatePicker,
|
|
146
148
|
ITDropdownMenu, ITField, ITMaskedInput, ITMultiSelect,
|
|
147
149
|
ITDialog, ITDivider, ITDrawer,
|
|
148
|
-
ITDropfile, ITEmptyState, ITFlex, ITFormBuilder, ITFormHeader, ITGrid, ITImage, ITInput, ITLayout,
|
|
150
|
+
ITDropfile, ITEmptyState, ITFlex, ITFormBuilder, ITFormHeader, ITGrid, ITImage, ITInput, ITInputNumber, ITLayout,
|
|
149
151
|
ITLoader, ITNavbar, ITPage, ITPageHeader, ITPagination, ITPopover, ITProgress,
|
|
150
152
|
ITRadioGroup, ITSelect, ITSearchSelect,
|
|
151
153
|
ITSearchTable,
|
|
@@ -169,10 +171,11 @@ export {
|
|
|
169
171
|
ITDialogProps, ITDividerProps, ITDrawerProps,
|
|
170
172
|
ITEmptyStateProps, ITFlexProps,
|
|
171
173
|
ITFormBuilderProps, ITFormHeaderProps,
|
|
172
|
-
ITGridProps, ITImageProps, ITInputProps,
|
|
174
|
+
ITGridProps, ITImageProps, ITInputProps, ITInputNumberProps,
|
|
173
175
|
ITLayoutProps, ITLoaderProps, ITNavbarProps,
|
|
174
176
|
ITPageProps, ITPageHeaderProps,
|
|
175
177
|
ITNavigationItem, ITNavigationSubItem,
|
|
178
|
+
ITNavigationSubItemEntry, ITNavigationSubItemGroup,
|
|
176
179
|
ITPaginationProps, ITPopoverProps, ITProgressProps,
|
|
177
180
|
ITRadioGroupProps, ITRadioOption, ITSelectProps, ITSearchSelectProps,
|
|
178
181
|
ITSearchTableProps,
|
|
@@ -56,6 +56,7 @@ export const SANDBOX_GROUPS: SandboxGroup[] = [
|
|
|
56
56
|
subitems: [
|
|
57
57
|
{ id: "button", label: "ITButton" },
|
|
58
58
|
{ id: "input", label: "ITInput" },
|
|
59
|
+
{ id: "inputnumber", label: "ITInputNumber" },
|
|
59
60
|
{ id: "select", label: "ITSelect" },
|
|
60
61
|
{ id: "searchselect", label: "ITSearchSelect" },
|
|
61
62
|
{ id: "multiselect", label: "ITMultiSelect" },
|
|
@@ -66,6 +67,7 @@ export const SANDBOX_GROUPS: SandboxGroup[] = [
|
|
|
66
67
|
{ id: "maskedinput", label: "ITMaskedInput" },
|
|
67
68
|
{ id: "calendar", label: "ITCalendar" },
|
|
68
69
|
{ id: "slidetoggle", label: "ITSlideToggle" },
|
|
70
|
+
{ id: "segmentedcontrol", label: "ITSegmentedControl" },
|
|
69
71
|
{ id: "dropfile", label: "ITDropfile" },
|
|
70
72
|
{ id: "wysiwyg", label: "ITWysiwyg" },
|
|
71
73
|
{ id: "formbuilder", label: "ITFormBuilder" },
|
|
@@ -248,6 +248,15 @@ export const TableShowcase = () => {
|
|
|
248
248
|
truncate: col.key === "email" || col.key === "name",
|
|
249
249
|
}));
|
|
250
250
|
|
|
251
|
+
// Pixel widths force horizontal overflow inside the 520px demo wrapper, so
|
|
252
|
+
// the first (Name) and last (Actions) columns can stay pinned while the
|
|
253
|
+
// middle ones scroll sideways.
|
|
254
|
+
const pinnedColumns: any = fixedColumns.map((col: any, i: number) => ({
|
|
255
|
+
...col,
|
|
256
|
+
width: [200, 240, 180, 160, 180, 96][i],
|
|
257
|
+
pinned: i === 0 ? "left" : i === fixedColumns.length - 1 ? "right" : undefined,
|
|
258
|
+
}));
|
|
259
|
+
|
|
251
260
|
// Single-line columns for virtualization: no avatar, no actions, no custom
|
|
252
261
|
// render, so every row is a deterministic one-line box.
|
|
253
262
|
const virtualColumns: any = [
|
|
@@ -344,6 +353,7 @@ export const TableShowcase = () => {
|
|
|
344
353
|
'<ITTable columns={columns} data={data} title="Users" />',
|
|
345
354
|
'<ITTable columns={columns} data={data} defaultView="cards" onRowClick={(row) => open(row)} />',
|
|
346
355
|
'<ITTable columns={fixedColumns} data={data} layout="fixed" density="compact" />',
|
|
356
|
+
'// Columna fija: pinned "left" | "right" (los offsets acumulan width en px)\n{ key: "name", label: "Name", type: "string", width: 200, pinned: "left" },\n{ key: "actions", label: "", type: "actions", width: 96, pinned: "right" }',
|
|
347
357
|
'<ITTable columns={cols} data={rows} virtualized stickyHeader layout="fixed" density="compact" defaultItemsPerPage={300} itemsPerPageOptions={[50, 100, 300]} />',
|
|
348
358
|
'// ITSearchSelect typeahead (server-side search opcional vía catalogOptions.onSearch)\n{ key: "role", label: "Role", type: "string", filter: "search", catalogOptions: { data: roles } }',
|
|
349
359
|
'// ITDatePicker fecha única\n{ key: "lastLogin", label: "Last login", type: "date", filter: "date", dateFilterOptions: { maxDate: new Date() } }',
|
|
@@ -377,6 +387,7 @@ export const TableShowcase = () => {
|
|
|
377
387
|
"onRowClick funciona en ambas vistas (tabla y cards).",
|
|
378
388
|
"Cuando se omite no se agrega cursor-pointer, foco, tabIndex ni role.",
|
|
379
389
|
"Column acepta width (number=px | string CSS), minWidth (px), align (left/center/right) y truncate (ellipsis + title nativo).",
|
|
390
|
+
"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.",
|
|
380
391
|
"Anti scroll horizontal: layout=\"fixed\" + width por columna + truncate + density.",
|
|
381
392
|
"Virtualización: solo vista tabla; rinde con page size grande; filas de alto uniforme; el scroll no se reinicia al cambiar filtros.",
|
|
382
393
|
"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.",
|
|
@@ -483,6 +494,23 @@ export const TableShowcase = () => {
|
|
|
483
494
|
</div>
|
|
484
495
|
</div>
|
|
485
496
|
|
|
497
|
+
<div>
|
|
498
|
+
<h4 className="text-sm font-bold text-slate-700 dark:text-slate-200 mb-3">Pinned columns — left (Name) & right (Actions)</h4>
|
|
499
|
+
<div className="border border-slate-200 dark:border-slate-700 rounded-xl overflow-hidden" style={{ maxWidth: 520 }}>
|
|
500
|
+
<ITTable
|
|
501
|
+
columns={pinnedColumns}
|
|
502
|
+
data={TABLE_DATA}
|
|
503
|
+
title="Pinned columns"
|
|
504
|
+
layout="fixed"
|
|
505
|
+
density="compact"
|
|
506
|
+
defaultItemsPerPage={5}
|
|
507
|
+
/>
|
|
508
|
+
</div>
|
|
509
|
+
<p className="mt-2 text-xs text-slate-500 dark:text-slate-400">
|
|
510
|
+
Scroll the table sideways: <code>pinned: "left"</code> freezes the Name column, <code>pinned: "right"</code> freezes the Actions column. Offsets accumulate from each preceding pinned column's pixel <code>width</code>.
|
|
511
|
+
</p>
|
|
512
|
+
</div>
|
|
513
|
+
|
|
486
514
|
<div>
|
|
487
515
|
<h4 className="text-sm font-bold text-slate-700 dark:text-slate-200 mb-3">Auto cards fallback (< 640px container)</h4>
|
|
488
516
|
<div className="border border-slate-200 dark:border-slate-700 rounded-xl overflow-hidden" style={{ maxWidth: 420 }}>
|
|
@@ -598,6 +626,19 @@ export const DataTableShowcase = () => {
|
|
|
598
626
|
{ key: "active", label: "Active", type: "boolean" as const, filter: true }
|
|
599
627
|
], []);
|
|
600
628
|
|
|
629
|
+
// Pixel widths force horizontal overflow inside the 520px demo wrapper, so
|
|
630
|
+
// the first (ID) and last (Active) columns stay pinned while the middle
|
|
631
|
+
// ones scroll sideways.
|
|
632
|
+
const pinnedColumns = useMemo(
|
|
633
|
+
() =>
|
|
634
|
+
userColumns.map((col: any, i: number) => ({
|
|
635
|
+
...col,
|
|
636
|
+
width: [72, 220, 180, 180, 120][i],
|
|
637
|
+
pinned: i === 0 ? "left" : i === userColumns.length - 1 ? "right" : undefined,
|
|
638
|
+
})),
|
|
639
|
+
[userColumns]
|
|
640
|
+
);
|
|
641
|
+
|
|
601
642
|
const MOCK_USERS = useMemo(() =>
|
|
602
643
|
[
|
|
603
644
|
{ id: 1, name: "Sofía Castillo", role: "Admin", active: true },
|
|
@@ -875,6 +916,7 @@ export const DataTableShowcase = () => {
|
|
|
875
916
|
'<ITDataTable columns={columns} fetchData={api.fetchUsers} />',
|
|
876
917
|
'<ITDataTable columns={columns} fetchData={api.fetchUsers} onRowClick={(row) => open(row)} />',
|
|
877
918
|
'<ITDataTable columns={fixedColumns} fetchData={api.fetchUsers} layout="fixed" density="compact" />',
|
|
919
|
+
'// Columna fija: pinned "left" | "right" (los offsets acumulan width en px)\n{ key: "id", label: "ID", type: "number", width: 72, pinned: "left" },\n{ key: "active", label: "Active", type: "boolean", width: 120, pinned: "right" }',
|
|
878
920
|
'<ITDataTable columns={cols} fetchData={api.page} virtualized stickyHeader defaultItemsPerPage={200} itemsPerPageOptions={[50, 100, 200]} debounceMs={0} />',
|
|
879
921
|
'<ITDataTable columns={cols} fetchData={api.page} layout="fixed" density="compact" autoCardBreakpoint={640} />',
|
|
880
922
|
'// Filtro de columna tipo ITSearchSelect (typeahead)\n{ key: "role", label: "Role", type: "string", filter: "search", catalogOptions: { data: roles, onSearch: (q) => api.searchRoles(q) } }',
|
|
@@ -913,6 +955,7 @@ export const DataTableShowcase = () => {
|
|
|
913
955
|
"onRowClick funciona en ambas vistas (tabla y cards).",
|
|
914
956
|
"Interacciones que inician en botones, links o inputs no disparan onRowClick.",
|
|
915
957
|
"Column acepta width (number=px | string CSS), minWidth (px), align (left/center/right) y truncate (ellipsis + title nativo).",
|
|
958
|
+
"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.",
|
|
916
959
|
"Anti scroll horizontal: layout=\"fixed\" + width por columna + truncate + density.",
|
|
917
960
|
"Virtualización: solo vista tabla; rinde con page size grande; filas de alto uniforme; el scroll no se reinicia al cambiar filtros.",
|
|
918
961
|
"La virtualización server-side hace ventana sobre la PÁGINA ACTUAL (data.length), no sobre total: usar un defaultItemsPerPage grande (p. ej. 200) con itemsPerPageOptions que lo incluya, o el efecto es invisible.",
|
|
@@ -980,6 +1023,24 @@ export const DataTableShowcase = () => {
|
|
|
980
1023
|
</div>
|
|
981
1024
|
</div>
|
|
982
1025
|
|
|
1026
|
+
<div>
|
|
1027
|
+
<h4 className="text-sm font-bold text-slate-700 dark:text-slate-200 mb-3">Pinned columns — left (ID) & right (Active)</h4>
|
|
1028
|
+
<div className="border border-slate-200 dark:border-slate-700 rounded-xl overflow-hidden" style={{ maxWidth: 520 }}>
|
|
1029
|
+
<ITDataTable
|
|
1030
|
+
columns={pinnedColumns}
|
|
1031
|
+
fetchData={fetchData}
|
|
1032
|
+
title="Pinned columns"
|
|
1033
|
+
layout="fixed"
|
|
1034
|
+
density="compact"
|
|
1035
|
+
defaultItemsPerPage={4}
|
|
1036
|
+
debounceMs={0}
|
|
1037
|
+
/>
|
|
1038
|
+
</div>
|
|
1039
|
+
<p className="mt-2 text-xs text-slate-500 dark:text-slate-400">
|
|
1040
|
+
Scroll the table sideways: <code>pinned: "left"</code> freezes the ID column, <code>pinned: "right"</code> freezes the Active column.
|
|
1041
|
+
</p>
|
|
1042
|
+
</div>
|
|
1043
|
+
|
|
983
1044
|
<div>
|
|
984
1045
|
<h4 className="text-sm font-bold text-slate-700 dark:text-slate-200 mb-3">Server-side virtualization — 240 rows {dtVirtualized ? "(page 200)" : "(page 10)"}</h4>
|
|
985
1046
|
<div className="border border-slate-200 dark:border-slate-700 rounded-xl overflow-hidden">
|