@axzydev/axzy_ui_system 1.3.1 → 1.3.3
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 +6 -2
- package/dist/index.cjs +957 -554
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.cts +191 -9
- package/dist/index.d.ts +191 -9
- package/dist/index.js +908 -505
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/App.tsx +150 -154
- package/src/components/molecules/table/table.doc.mdx +64 -21
- package/src/components/molecules/table/table.props.ts +81 -2
- package/src/components/molecules/table/table.stories.tsx +155 -0
- package/src/components/molecules/table/table.tsx +178 -36
- package/src/components/organisms/data-table/ITDataTable.doc.mdx +37 -3
- package/src/components/organisms/data-table/ITDataTable.stories.tsx +43 -0
- package/src/components/organisms/data-table/dataTable.props.ts +64 -0
- package/src/components/organisms/data-table/dataTable.tsx +241 -82
- package/src/components/organisms/sidebar/sidebar.doc.mdx +5 -0
- package/src/components/organisms/sidebar/sidebar.props.ts +5 -0
- package/src/components/organisms/sidebar/sidebar.stories.tsx +1 -1
- package/src/components/organisms/sidebar/sidebar.tsx +27 -7
- package/src/components/organisms/topbar/topbar.doc.mdx +3 -0
- package/src/components/organisms/topbar/topbar.props.ts +16 -0
- package/src/components/organisms/topbar/topbar.tsx +133 -98
- package/src/components/templates/layout/layout.doc.mdx +2 -0
- package/src/components/templates/layout/layout.tsx +7 -1
- package/src/components/templates/page/page.doc.mdx +8 -2
- package/src/components/templates/page/page.props.ts +11 -1
- package/src/components/templates/page/page.tsx +11 -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/dev.css +58 -1
- package/src/hooks/useElementSize.ts +46 -0
- package/src/hooks/useVirtualRows.ts +123 -0
- package/src/index.ts +2 -0
- package/src/sandbox/LandingShowcase.tsx +654 -0
- package/src/sandbox/NotFoundShowcase.tsx +70 -0
- package/src/sandbox/TopbarSearch.tsx +85 -0
- package/src/sandbox/navigation.ts +148 -0
- package/src/sandbox/useHashRoute.ts +149 -0
- package/src/showcases/DataShowcases.tsx +321 -24
- package/src/showcases/PageShowcases.tsx +31 -0
- package/src/showcases/StructureShowcases.tsx +42 -4
- package/src/theme/theme.types.ts +2 -0
- package/src/types/table.types.ts +48 -0
- package/src/utils/styles.ts +12 -5
- package/src/utils/table.utils.ts +31 -0
package/package.json
CHANGED
package/src/App.tsx
CHANGED
|
@@ -7,7 +7,8 @@ import {
|
|
|
7
7
|
FaSlidersH,
|
|
8
8
|
FaTable,
|
|
9
9
|
} from "react-icons/fa";
|
|
10
|
-
import { ITLayout, ITThemeProvider } from "./index";
|
|
10
|
+
import { ITLayout, ITThemeProvider, useDebouncedSearch } from "./index";
|
|
11
|
+
import type { ITNavigationItem } from "./index";
|
|
11
12
|
import "./index.css";
|
|
12
13
|
|
|
13
14
|
// Import Showcases
|
|
@@ -69,187 +70,165 @@ import {
|
|
|
69
70
|
ThemeProviderShowcase,
|
|
70
71
|
} from "./showcases/FeedbackShowcases";
|
|
71
72
|
|
|
72
|
-
|
|
73
|
+
// Sandbox routing / landing (dev-only, never published)
|
|
74
|
+
import {
|
|
75
|
+
SANDBOX_GROUPS,
|
|
76
|
+
firstItemOf,
|
|
77
|
+
} from "./sandbox/navigation";
|
|
78
|
+
import { useHashRoute } from "./sandbox/useHashRoute";
|
|
79
|
+
import TopbarSearch from "./sandbox/TopbarSearch";
|
|
80
|
+
import { LandingShowcase } from "./sandbox/LandingShowcase";
|
|
81
|
+
import NotFoundShowcase from "./sandbox/NotFoundShowcase";
|
|
73
82
|
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
{ id: "sizes", label: "Medidas · sm / md / lg" },
|
|
84
|
-
],
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
id: "struc",
|
|
88
|
-
label: "Estructura & Layout",
|
|
89
|
-
icon: <FaCreditCard />,
|
|
90
|
-
subitems: [
|
|
91
|
-
{ id: "layout", label: "ITLayout & ITNavbar" },
|
|
92
|
-
{ id: "stack", label: "ITStack" },
|
|
93
|
-
{ id: "flex", label: "ITFlex" },
|
|
94
|
-
{ id: "grid", label: "ITGrid" },
|
|
95
|
-
{ id: "card", label: "ITCard" },
|
|
96
|
-
{ id: "text", label: "ITText" },
|
|
97
|
-
{ id: "accordion", label: "ITAccordion" },
|
|
98
|
-
{ id: "pageheader", label: "ITPageHeader" },
|
|
99
|
-
{ id: "page", label: "ITPage" },
|
|
100
|
-
{ id: "screen-dashboard", label: "Dashboard Ejemplo" },
|
|
101
|
-
{ id: "screen-form", label: "Formulario Ejemplo" },
|
|
102
|
-
],
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
id: "forms",
|
|
106
|
-
label: "Formularios & Inputs",
|
|
107
|
-
icon: <FaKeyboard />,
|
|
108
|
-
subitems: [
|
|
109
|
-
{ id: "button", label: "ITButton" },
|
|
110
|
-
{ id: "input", label: "ITInput" },
|
|
111
|
-
{ id: "select", label: "ITSelect" },
|
|
112
|
-
{ id: "searchselect", label: "ITSearchSelect" },
|
|
113
|
-
{ id: "multiselect", label: "ITMultiSelect" },
|
|
114
|
-
{ id: "chipinput", label: "ITChipInput" },
|
|
115
|
-
{ id: "field", label: "ITField" },
|
|
116
|
-
{ id: "datepicker", label: "ITDatePicker" },
|
|
117
|
-
{ id: "timepicker", label: "ITTimePicker" },
|
|
118
|
-
{ id: "maskedinput", label: "ITMaskedInput" },
|
|
119
|
-
{ id: "calendar", label: "ITCalendar" },
|
|
120
|
-
{ id: "slidetoggle", label: "ITSlideToggle" },
|
|
121
|
-
{ id: "dropfile", label: "ITDropfile" },
|
|
122
|
-
{ id: "wysiwyg", label: "ITWysiwyg" },
|
|
123
|
-
{ id: "formbuilder", label: "ITFormBuilder" },
|
|
124
|
-
],
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
id: "data",
|
|
128
|
-
label: "Visualización Datos",
|
|
129
|
-
icon: <FaTable />,
|
|
130
|
-
subitems: [
|
|
131
|
-
{ id: "table", label: "ITTable" },
|
|
132
|
-
{ id: "datatable", label: "ITDataTable" },
|
|
133
|
-
{ id: "badget", label: "ITBadget" },
|
|
134
|
-
{ id: "chip", label: "ITChip" },
|
|
135
|
-
{ id: "image", label: "ITImage" },
|
|
136
|
-
],
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
id: "nav",
|
|
140
|
-
label: "Navegación & Control",
|
|
141
|
-
icon: <FaSlidersH />,
|
|
142
|
-
subitems: [
|
|
143
|
-
{ id: "tabs", label: "ITTabs" },
|
|
144
|
-
{ id: "stepper", label: "ITStepper" },
|
|
145
|
-
{ id: "pagination", label: "ITPagination" },
|
|
146
|
-
{ id: "triplefilter", label: "ITTripleFilter" },
|
|
147
|
-
{ id: "dropdownmenu", label: "ITDropdownMenu" },
|
|
148
|
-
],
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
id: "feed",
|
|
152
|
-
label: "Feedback & Sistema",
|
|
153
|
-
icon: <FaRegBell />,
|
|
154
|
-
subitems: [
|
|
155
|
-
{ id: "dialog", label: "ITDialog" },
|
|
156
|
-
{ id: "toast", label: "ITToast" },
|
|
157
|
-
{ id: "loader", label: "ITLoader" },
|
|
158
|
-
{ id: "themeprovider", label: "ITThemeProvider" },
|
|
159
|
-
],
|
|
160
|
-
},
|
|
161
|
-
];
|
|
83
|
+
// Icons per sandbox group (icons stay here; navigation.ts is plain data).
|
|
84
|
+
const GROUP_ICONS: Record<string, React.ReactNode> = {
|
|
85
|
+
general: <FaHome />,
|
|
86
|
+
struc: <FaCreditCard />,
|
|
87
|
+
forms: <FaKeyboard />,
|
|
88
|
+
data: <FaTable />,
|
|
89
|
+
nav: <FaSlidersH />,
|
|
90
|
+
feed: <FaRegBell />,
|
|
91
|
+
};
|
|
162
92
|
|
|
163
93
|
function App() {
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
return hash.startsWith("ui-system") ? "ui-system" : "home";
|
|
167
|
-
});
|
|
168
|
-
const [showroomActive, setShowroomActive] = useState("screen-form");
|
|
169
|
-
const [searchTerm] = useState("");
|
|
170
|
-
const [subitemConnector] = useState<
|
|
171
|
-
"dot" | "|" | "none"
|
|
172
|
-
>("dot");
|
|
94
|
+
const { route, goToItem, goToLanding, goToPortfolio } = useHashRoute();
|
|
95
|
+
const [subitemConnector] = useState<"dot" | "|" | "none">("dot");
|
|
173
96
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}, []);
|
|
97
|
+
// Debounced term drives filtering; `searchValue` is the live input value.
|
|
98
|
+
const [debouncedTerm, setDebouncedTerm] = useState("");
|
|
99
|
+
const {
|
|
100
|
+
searchTerm: searchValue,
|
|
101
|
+
handleSearchChange,
|
|
102
|
+
handleClearSearch,
|
|
103
|
+
} = useDebouncedSearch({ debounceMs: 250, onSearch: setDebouncedTerm });
|
|
182
104
|
|
|
183
|
-
//
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
.
|
|
187
|
-
|
|
188
|
-
const matches =
|
|
189
|
-
cat.label.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
190
|
-
cat.id.toLowerCase().includes(searchTerm.toLowerCase());
|
|
191
|
-
if (matches) {
|
|
192
|
-
return {
|
|
193
|
-
...cat,
|
|
194
|
-
isActive: showroomActive === cat.id,
|
|
195
|
-
action: () => setShowroomActive(cat.id),
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
return null;
|
|
199
|
-
}
|
|
105
|
+
// The active component page (group-only routes fall back to the first item).
|
|
106
|
+
const showroomActive =
|
|
107
|
+
route.view === "ui-system" && route.valid
|
|
108
|
+
? route.item ?? (route.group ? firstItemOf(route.group) : null)
|
|
109
|
+
: null;
|
|
200
110
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
111
|
+
// Redirect `#ui-system/<group>` to the group's first item (replace, no history spam).
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (route.view === "ui-system" && route.valid && route.group && !route.item) {
|
|
114
|
+
const first = firstItemOf(route.group);
|
|
115
|
+
if (first) goToItem(route.group, first, { replace: true });
|
|
116
|
+
}
|
|
117
|
+
}, [route, goToItem]);
|
|
206
118
|
|
|
207
|
-
|
|
119
|
+
// Sidebar navigation: grouped when idle, flat filtered leaves when searching.
|
|
120
|
+
const navigationItems = useMemo<ITNavigationItem[]>(() => {
|
|
121
|
+
const term = debouncedTerm.trim().toLowerCase();
|
|
122
|
+
|
|
123
|
+
if (!term) {
|
|
124
|
+
return SANDBOX_GROUPS.map((group) => {
|
|
125
|
+
const subitems = group.subitems.map((sub) => ({
|
|
208
126
|
id: sub.id,
|
|
209
127
|
label: sub.label,
|
|
210
128
|
isActive: showroomActive === sub.id,
|
|
211
|
-
action: () =>
|
|
129
|
+
action: () => goToItem(group.id, sub.id),
|
|
212
130
|
}));
|
|
213
|
-
|
|
214
|
-
const isAnySubitemActive = mappedSubitems.some((sub) => sub.isActive);
|
|
215
|
-
|
|
216
131
|
return {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
132
|
+
id: group.id,
|
|
133
|
+
label: group.label,
|
|
134
|
+
icon: GROUP_ICONS[group.id],
|
|
135
|
+
isActive: subitems.some((sub) => sub.isActive),
|
|
136
|
+
subitems,
|
|
220
137
|
};
|
|
221
|
-
})
|
|
222
|
-
.filter((cat): cat is Exclude<typeof cat, null> => {
|
|
223
|
-
if (!cat) return false;
|
|
224
|
-
if (cat.subitems) return cat.subitems.length > 0;
|
|
225
|
-
return true;
|
|
226
138
|
});
|
|
227
|
-
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const flat: ITNavigationItem[] = [];
|
|
142
|
+
SANDBOX_GROUPS.forEach((group) => {
|
|
143
|
+
const groupMatches =
|
|
144
|
+
group.label.toLowerCase().includes(term) ||
|
|
145
|
+
group.id.toLowerCase().includes(term);
|
|
146
|
+
const groupBadge = group.label.split(" ")[0];
|
|
147
|
+
|
|
148
|
+
group.subitems
|
|
149
|
+
.filter(
|
|
150
|
+
(sub) =>
|
|
151
|
+
groupMatches ||
|
|
152
|
+
sub.label.toLowerCase().includes(term) ||
|
|
153
|
+
sub.id.toLowerCase().includes(term),
|
|
154
|
+
)
|
|
155
|
+
.forEach((sub) => {
|
|
156
|
+
flat.push({
|
|
157
|
+
id: sub.id,
|
|
158
|
+
label: sub.label,
|
|
159
|
+
icon: GROUP_ICONS[group.id],
|
|
160
|
+
badge: groupBadge,
|
|
161
|
+
isActive: showroomActive === sub.id,
|
|
162
|
+
action: () => goToItem(group.id, sub.id),
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
return flat;
|
|
167
|
+
}, [debouncedTerm, showroomActive, goToItem]);
|
|
228
168
|
|
|
169
|
+
// Sidebar is UNCONTROLLED: ITLayout's internal default is the 88px rail and the
|
|
170
|
+
// rail expands automatically on hover. No toggle button — the hover is the control.
|
|
171
|
+
// The component search lives in the topbar because a hover-only rail can't host it.
|
|
229
172
|
const sidebarProps = {
|
|
230
|
-
navigationItems
|
|
173
|
+
navigationItems,
|
|
231
174
|
subitemConnector,
|
|
232
175
|
};
|
|
233
176
|
|
|
234
177
|
const topBarProps = {
|
|
235
178
|
logoText: "AXZY UI System",
|
|
179
|
+
centerContent: (
|
|
180
|
+
<TopbarSearch
|
|
181
|
+
value={searchValue}
|
|
182
|
+
onChange={handleSearchChange}
|
|
183
|
+
onClear={handleClearSearch}
|
|
184
|
+
resultCount={navigationItems.length}
|
|
185
|
+
pending={searchValue.trim() !== debouncedTerm.trim()}
|
|
186
|
+
/>
|
|
187
|
+
),
|
|
188
|
+
// The topbar places `children` in its right area, right before the user menu.
|
|
189
|
+
children: (
|
|
190
|
+
<button
|
|
191
|
+
type="button"
|
|
192
|
+
aria-label="Notificaciones"
|
|
193
|
+
title="Notificaciones"
|
|
194
|
+
className="relative flex items-center justify-center p-2.5 rounded-xl transition-colors duration-200 hover:bg-[var(--it-topbar-user-hover,#f1f5f9)] focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary-400"
|
|
195
|
+
style={{ color: "var(--it-topbar-icon, #94a3b8)" }}
|
|
196
|
+
>
|
|
197
|
+
<FaRegBell className="w-5 h-5" aria-hidden="true" />
|
|
198
|
+
<span className="absolute top-1.5 right-1.5 w-2 h-2 rounded-full bg-red-500 ring-2 ring-white" />
|
|
199
|
+
</button>
|
|
200
|
+
),
|
|
201
|
+
navItems: [
|
|
202
|
+
{
|
|
203
|
+
id: "ui-system",
|
|
204
|
+
label: "UI System",
|
|
205
|
+
action: () => goToLanding(),
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
id: "portfolio",
|
|
209
|
+
label: "Portafolio",
|
|
210
|
+
action: () => goToPortfolio(),
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
onNavItemClick: (id: string) => {
|
|
214
|
+
if (id === "ui-system") goToLanding();
|
|
215
|
+
else if (id === "portfolio") goToPortfolio();
|
|
216
|
+
},
|
|
236
217
|
userMenu: {
|
|
237
218
|
userName: "Alex Dev",
|
|
238
219
|
userEmail: "alex@axzy.dev",
|
|
239
220
|
menuItems: [
|
|
240
221
|
{
|
|
241
222
|
label: "Ir al Portafolio",
|
|
242
|
-
onClick: () =>
|
|
243
|
-
window.location.hash = "home";
|
|
244
|
-
},
|
|
223
|
+
onClick: () => goToPortfolio(),
|
|
245
224
|
},
|
|
246
225
|
],
|
|
247
226
|
},
|
|
248
227
|
};
|
|
249
228
|
|
|
250
229
|
// Render correct component based on active showroom navigation
|
|
251
|
-
const renderShowcase = () => {
|
|
252
|
-
switch (
|
|
230
|
+
const renderShowcase = (itemId: string | null) => {
|
|
231
|
+
switch (itemId) {
|
|
253
232
|
case "getting-started":
|
|
254
233
|
return <GettingStartedShowcase />;
|
|
255
234
|
// Structure
|
|
@@ -340,13 +319,19 @@ function App() {
|
|
|
340
319
|
case "themeprovider":
|
|
341
320
|
return <ThemeProviderShowcase />;
|
|
342
321
|
default:
|
|
343
|
-
return
|
|
322
|
+
return (
|
|
323
|
+
<NotFoundShowcase
|
|
324
|
+
attempted={route.raw}
|
|
325
|
+
onGoLanding={() => goToLanding()}
|
|
326
|
+
onGoPortfolio={goToPortfolio}
|
|
327
|
+
/>
|
|
328
|
+
);
|
|
344
329
|
}
|
|
345
330
|
};
|
|
346
331
|
|
|
347
332
|
return (
|
|
348
333
|
<ITThemeProvider showFab={false} density={0.98} radius={10} shadow={2}>
|
|
349
|
-
{view === "home" ? (
|
|
334
|
+
{route.view === "home" ? (
|
|
350
335
|
<div className="min-h-screen bg-slate-50 dark:bg-slate-950 py-10 px-4 sm:px-6 lg:px-8">
|
|
351
336
|
<div className="max-w-6xl mx-auto">
|
|
352
337
|
<HomeShowcase />
|
|
@@ -354,9 +339,20 @@ function App() {
|
|
|
354
339
|
</div>
|
|
355
340
|
) : (
|
|
356
341
|
<ITLayout sidebar={sidebarProps} topBar={topBarProps}>
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
342
|
+
{!route.valid ? (
|
|
343
|
+
<NotFoundShowcase
|
|
344
|
+
attempted={route.raw}
|
|
345
|
+
onGoLanding={() => goToLanding()}
|
|
346
|
+
onGoPortfolio={goToPortfolio}
|
|
347
|
+
/>
|
|
348
|
+
) : !route.group && !route.item ? (
|
|
349
|
+
<LandingShowcase
|
|
350
|
+
onOpenItem={goToItem}
|
|
351
|
+
onOpenPortfolio={goToPortfolio}
|
|
352
|
+
/>
|
|
353
|
+
) : (
|
|
354
|
+
renderShowcase(showroomActive)
|
|
355
|
+
)}
|
|
360
356
|
</ITLayout>
|
|
361
357
|
)}
|
|
362
358
|
</ITThemeProvider>
|
|
@@ -11,15 +11,37 @@ Basic data table with column definitions, sorting, and configurable visual varia
|
|
|
11
11
|
|
|
12
12
|
| Prop | Type | Default | Description |
|
|
13
13
|
|------|------|---------|-------------|
|
|
14
|
-
| `columns` | `
|
|
15
|
-
| `data` | `
|
|
16
|
-
| `variant` | `
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
14
|
+
| `columns` | `Column<T>[]` | — | Column definitions: key, label, type, sortable, filter, render, actions, plus layout fields `width` (`number` px or CSS `string`), `minWidth` (px), `align` (`"left" \| "center" \| "right"`), and `truncate` (ellipsis + native `title`). |
|
|
15
|
+
| `data` | `T[]` | `[]` | Row data array. |
|
|
16
|
+
| `variant` | `"default" \| "striped" \| "bordered"` | `"default"` | Visual style variant. |
|
|
17
|
+
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Font-size preset (does not change padding). |
|
|
18
|
+
| `containerClassName` | `string` | — | CSS classes for the outermost container. |
|
|
19
|
+
| `className` | `string` | — | Declared for API compatibility but **currently not applied** by ITTable. Use `containerClassName` for the outer wrapper. |
|
|
20
|
+
| `itemsPerPageOptions` | `number[]` | `[5, 10, 20]` | Page-size options shown in the paginator. |
|
|
21
|
+
| `defaultItemsPerPage` | `number` | `10` | Initial rows per page. |
|
|
22
|
+
| `title` | `string` | — | Optional heading rendered above the table. |
|
|
23
|
+
| `renderCard` | `(row: T) => ReactNode` | — | Custom card renderer for the cards view. |
|
|
24
|
+
| `defaultView` | `"table" \| "cards"` | `"table"` | Initial view mode. |
|
|
25
|
+
| `showVerticalBorder` | `boolean` | `true` | Show vertical borders between columns. |
|
|
26
|
+
| `verticalBorderClassname` | `string` | — | Custom class for the vertical borders. |
|
|
27
|
+
| `onRowClick` | `(row: T, event: MouseEvent<HTMLElement> \| KeyboardEvent<HTMLElement>) => void` | — | Row/card activation. `MouseEvent` for pointer, `KeyboardEvent` for Enter/Space. Skipped when the interaction starts inside an interactive child. |
|
|
28
|
+
| `layout` | `"auto" \| "fixed"` | `"auto"` | Table layout algorithm. `"fixed"` honors column `width` values and drops the `min-w-max` / `min-w-[150px]` floors so content no longer forces horizontal scrolling. |
|
|
29
|
+
| `density` | `"compact" \| "normal" \| "comfortable"` | `"normal"` | Cell padding / row-height preset. Independent of `size`, which only controls font-size. |
|
|
30
|
+
| `autoCardBreakpoint` | `number` | `0` | Container width (px) below which the table automatically falls back to the cards view. `0` disables the fallback. |
|
|
31
|
+
| `virtualized` | `boolean` | `false` | Renders only the visible row window with spacer rows preserving scroll height. Table view only; cards view is never virtualized. |
|
|
32
|
+
| `virtualizedMaxHeight` | `number` | `400` | Max height (px) of the virtualized scroll container. Only used when `virtualized` is true. |
|
|
33
|
+
| `rowHeight` | `number` | `getRowHeight(size, density)` | Assumed uniform row height (px). Size-aware: `size="md"` baseline is 33 / 45 / 53 for compact / normal / comfortable; `size` shifts it by the font-size line box (sm 16px / md 20px / lg 28px). |
|
|
34
|
+
| `overscan` | `number` | `5` | Extra rows rendered above and below the viewport to avoid blank gaps. |
|
|
35
|
+
| `stickyHeader` | `boolean` | `false` | Keeps header cells sticky while the virtualized body scrolls. Only effective when `virtualized` is true. |
|
|
36
|
+
|
|
37
|
+
### Column fields
|
|
38
|
+
|
|
39
|
+
| Field | Type | Default | Description |
|
|
40
|
+
|-------|------|---------|-------------|
|
|
41
|
+
| `width` | `number \| string` | — | Fixed column width. `number` = px, `string` = any CSS length (e.g. `"20%"`). A `<colgroup>` is only emitted when at least one column sets it. |
|
|
42
|
+
| `minWidth` | `number` | — | Minimum column width in px, applied to header and body cells via inline style. |
|
|
43
|
+
| `align` | `"left" \| "center" \| "right"` | `"left"` | Horizontal alignment of the column's header and cells. |
|
|
44
|
+
| `truncate` | `boolean` | `false` | Truncates overflowing cell content with an ellipsis and exposes the raw string/number via the native `title` attribute. |
|
|
23
45
|
|
|
24
46
|
## Usage
|
|
25
47
|
|
|
@@ -27,28 +49,49 @@ Basic data table with column definitions, sorting, and configurable visual varia
|
|
|
27
49
|
import { ITTable } from '@axzydev/axzy_ui_system';
|
|
28
50
|
|
|
29
51
|
const columns = [
|
|
30
|
-
{ key: 'name', label: 'Name', sortable: true },
|
|
31
|
-
{ key: 'role', label: 'Role' },
|
|
32
|
-
{ key: 'status', label: 'Status',
|
|
52
|
+
{ key: 'name', label: 'Name', type: 'string', sortable: true, filter: true },
|
|
53
|
+
{ key: 'role', label: 'Role', type: 'string', filter: 'catalog', catalogOptions: { data: roles } },
|
|
54
|
+
{ key: 'status', label: 'Status', type: 'boolean', filter: true },
|
|
33
55
|
];
|
|
34
56
|
const data = [
|
|
35
|
-
{ name: 'Alice', role: '
|
|
36
|
-
{ name: 'Bob', role: '
|
|
57
|
+
{ name: 'Alice', role: 'ADMIN', status: true },
|
|
58
|
+
{ name: 'Bob', role: 'EDITOR', status: false },
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
<ITTable columns={columns} data={data} title="Users" />
|
|
62
|
+
<ITTable columns={columns} data={data} variant="striped" size="sm" defaultItemsPerPage={5} />
|
|
63
|
+
<ITTable
|
|
64
|
+
columns={columns}
|
|
65
|
+
data={data}
|
|
66
|
+
defaultView="cards"
|
|
67
|
+
onRowClick={(row, event) => console.log(row, event.type)}
|
|
68
|
+
/>
|
|
69
|
+
|
|
70
|
+
// No horizontal scroll: fixed layout + per-column widths + truncation
|
|
71
|
+
const fixedColumns = [
|
|
72
|
+
{ key: 'name', label: 'Name', type: 'string', width: '30%', truncate: true },
|
|
73
|
+
{ key: 'email', label: 'Email', type: 'string', width: 220, truncate: true },
|
|
74
|
+
{ key: 'role', label: 'Role', type: 'string', width: 140, align: 'center' },
|
|
37
75
|
];
|
|
38
76
|
|
|
39
|
-
<ITTable columns={
|
|
40
|
-
<ITTable columns={columns} data={data} variant="striped" sortKey="name" sortDirection="asc" />
|
|
41
|
-
<ITTable columns={columns} data={data} variant="bordered" onRowClick={(row) => console.log(row)} />
|
|
42
|
-
<ITTable columns={columns} data={[]} emptyMessage="No records found" />
|
|
43
|
-
<ITTable columns={columns} data={data} loading />
|
|
77
|
+
<ITTable columns={fixedColumns} data={data} layout="fixed" density="compact" />
|
|
44
78
|
```
|
|
45
79
|
|
|
46
80
|
## Notes
|
|
47
81
|
|
|
48
82
|
- `columns[].render` provides custom cell rendering via render function
|
|
49
83
|
- `columns[].sortable` enables column header sort toggles
|
|
50
|
-
-
|
|
51
|
-
- `
|
|
84
|
+
- `onRowClick` fires in **both** the table and cards views. The event is a `MouseEvent` for pointer interaction and a `KeyboardEvent` for Enter/Space activation.
|
|
85
|
+
- Clicks/keys that start inside an interactive child (`button`, `a`, `input`, `select`, `textarea`, `[role="button"]`, `[role="link"]`, `[data-row-click-ignore]`) do **not** trigger `onRowClick`.
|
|
86
|
+
- When `onRowClick` is omitted no `cursor-pointer`, focus ring, `tabIndex`, or `role` is added.
|
|
87
|
+
- `density` controls padding/row height (`compact` 33px / `normal` 45px / `comfortable` 53px at `size="md"`); `size` changes font-size and shifts the row height by the line box (sm 16px / md 20px / lg 28px).
|
|
88
|
+
- **Anti horizontal scroll recipe:** `layout="fixed"` + a `width` on every column + `truncate` on long text + `density="compact"`. `width` accepts a `number` (px) or any CSS length string (`"20%"`, `"12rem"`). A `<colgroup>` is only emitted when at least one column defines a `width`.
|
|
89
|
+
- With the default `layout="auto"`, `min-w-max` stays active so column widths can still reflow as different cell content enters the window during vertical scroll. Use `layout="fixed"` + column `width` for stable widths.
|
|
90
|
+
- `autoCardBreakpoint` swaps to the cards view when the container (not the viewport) is narrower than the given px value; the Table toggle is disabled while forced, the Cards toggle stays enabled.
|
|
91
|
+
- **Virtualization** applies to the **table view only** — the cards view is never virtualized. It only pays off with a large page size: raise `defaultItemsPerPage` and `itemsPerPageOptions` (defaults are `10` and `[5, 10, 20]`). Rows are assumed to have a uniform `rowHeight`; use `truncate` + `layout="fixed"` for predictable heights.
|
|
92
|
+
- Scroll resets to the top on page or items-per-page change, but **not** on filter change (known limitation). `aria-rowcount` reflects the current page (+1), not the whole dataset, because pagination is independent.
|
|
93
|
+
- `virtualizedMaxHeight`, `rowHeight`, `overscan`, and `stickyHeader` only take effect when `virtualized` is true. `stickyHeader` makes each `<th>` sticky using the `--color-table-headerBg` token (fallback `#f8fafc`).
|
|
94
|
+
- `TableDensity` is exported from the package. `ITSearchTable` does **not** support `layout`, `density`, `autoCardBreakpoint`, `virtualized`, or per-column `width` — do not assume parity.
|
|
52
95
|
- Three visual variants: default (no borders), striped (alternating rows), bordered (full grid)
|
|
53
96
|
|
|
54
97
|
## Stories
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TableSize, TableVariants } from "@/types/table.types";
|
|
1
|
+
import { TableDensity, TableSize, TableVariants } from "@/types/table.types";
|
|
2
2
|
|
|
3
3
|
/** Data type of a table column, controls default rendering, filter UI, and sort comparison. */
|
|
4
4
|
export type ColumnType = "string" | "date" | "number" | "boolean" | "actions" | "catalog";
|
|
@@ -46,6 +46,22 @@ export interface Column<T = any> {
|
|
|
46
46
|
/** Shows an error state in the filter UI when the catalog failed to load. @default false */
|
|
47
47
|
error?: boolean;
|
|
48
48
|
};
|
|
49
|
+
/**
|
|
50
|
+
* Fixed column width. A `number` is treated as pixels (`width: 120` → `120px`);
|
|
51
|
+
* a `string` is passed through as any CSS length (`"20%"`, `"12rem"`).
|
|
52
|
+
* A `<colgroup>` is only rendered when at least one column defines a width.
|
|
53
|
+
*/
|
|
54
|
+
width?: number | string;
|
|
55
|
+
/** Minimum column width in pixels, applied to the header and body cells via inline style. */
|
|
56
|
+
minWidth?: number;
|
|
57
|
+
/** Horizontal alignment of this column's header and body cells. @default "left" */
|
|
58
|
+
align?: "left" | "center" | "right";
|
|
59
|
+
/**
|
|
60
|
+
* Truncates overflowing cell content with an ellipsis and exposes the raw
|
|
61
|
+
* string/number value through the native `title` attribute.
|
|
62
|
+
* @default false
|
|
63
|
+
*/
|
|
64
|
+
truncate?: boolean;
|
|
49
65
|
}
|
|
50
66
|
|
|
51
67
|
export interface ITTableProps<T> {
|
|
@@ -55,7 +71,7 @@ export interface ITTableProps<T> {
|
|
|
55
71
|
containerClassName?: string;
|
|
56
72
|
/** The data array to render in the table body. */
|
|
57
73
|
data: T[];
|
|
58
|
-
/** Visual variant: "default", "striped", "bordered"
|
|
74
|
+
/** Visual variant: "default", "striped", "bordered". */
|
|
59
75
|
variant?: TableVariants;
|
|
60
76
|
/** Additional CSS classes for the root table wrapper. */
|
|
61
77
|
className?: string;
|
|
@@ -75,4 +91,67 @@ export interface ITTableProps<T> {
|
|
|
75
91
|
showVerticalBorder?: boolean;
|
|
76
92
|
/** Custom class for vertical borders (overrides the default subtle gray). */
|
|
77
93
|
verticalBorderClassname?: string;
|
|
94
|
+
/**
|
|
95
|
+
* Callback fired when a row (table view) or card (cards view) is activated,
|
|
96
|
+
* receiving the row item and the originating event. The event is a
|
|
97
|
+
* `MouseEvent` for pointer interaction and a `KeyboardEvent` for Enter/Space
|
|
98
|
+
* activation. Not fired when the interaction starts inside an interactive
|
|
99
|
+
* child (button, link, input, or any `[data-row-click-ignore]` element).
|
|
100
|
+
*/
|
|
101
|
+
onRowClick?: (
|
|
102
|
+
row: T,
|
|
103
|
+
event: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>
|
|
104
|
+
) => void;
|
|
105
|
+
/**
|
|
106
|
+
* Table layout algorithm. `"fixed"` respects column `width` values and drops
|
|
107
|
+
* the `min-w-max` / `min-w-[150px]` floors so content no longer forces
|
|
108
|
+
* horizontal scrolling; `"auto"` keeps the content-driven layout.
|
|
109
|
+
* @default "auto"
|
|
110
|
+
*/
|
|
111
|
+
layout?: "auto" | "fixed";
|
|
112
|
+
/**
|
|
113
|
+
* Cell padding and row-height preset. Independent of `size`, which only
|
|
114
|
+
* controls font-size: `"compact"` | `"normal"` | `"comfortable"`.
|
|
115
|
+
* @default "normal"
|
|
116
|
+
*/
|
|
117
|
+
density?: TableDensity;
|
|
118
|
+
/**
|
|
119
|
+
* Container width (in px) below which the table automatically falls back to
|
|
120
|
+
* the cards view. `0` disables the fallback.
|
|
121
|
+
* @default 0
|
|
122
|
+
*/
|
|
123
|
+
autoCardBreakpoint?: number;
|
|
124
|
+
/**
|
|
125
|
+
* Enables row virtualization for the table view: only the visible window of
|
|
126
|
+
* rows is rendered, with spacer rows preserving the full scroll height.
|
|
127
|
+
* The cards view is never virtualized.
|
|
128
|
+
* @default false
|
|
129
|
+
*/
|
|
130
|
+
virtualized?: boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Max height (px) of the virtualized table's scroll container.
|
|
133
|
+
* Only used when `virtualized` is true.
|
|
134
|
+
* @default 400
|
|
135
|
+
*/
|
|
136
|
+
virtualizedMaxHeight?: number;
|
|
137
|
+
/**
|
|
138
|
+
* Assumed uniform row height (px) used by the virtualizer. Defaults to
|
|
139
|
+
* `getRowHeight(size, density)`, which is size-aware: the `size="md"`
|
|
140
|
+
* baseline is 33 / 45 / 53 (compact / normal / comfortable) and `size`
|
|
141
|
+
* shifts it by the font-size line box (sm 16px / md 20px / lg 28px).
|
|
142
|
+
* Only used when `virtualized` is true.
|
|
143
|
+
*/
|
|
144
|
+
rowHeight?: number;
|
|
145
|
+
/**
|
|
146
|
+
* Number of extra rows rendered above and below the viewport to avoid blank
|
|
147
|
+
* gaps while scrolling. Only used when `virtualized` is true.
|
|
148
|
+
* @default 5
|
|
149
|
+
*/
|
|
150
|
+
overscan?: number;
|
|
151
|
+
/**
|
|
152
|
+
* Makes the header cells sticky while the virtualized body scrolls. Only
|
|
153
|
+
* effective when `virtualized` is true.
|
|
154
|
+
* @default false
|
|
155
|
+
*/
|
|
156
|
+
stickyHeader?: boolean;
|
|
78
157
|
}
|