@axzydev/axzy_ui_system 1.3.3 → 1.3.5
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 +1195 -946
- 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 +90 -10
- package/dist/index.d.ts +90 -10
- package/dist/index.js +1189 -940
- package/dist/index.js.map +1 -1
- package/package.json +9 -3
- package/src/components/molecules/table/table.doc.mdx +23 -1
- package/src/components/molecules/table/table.props.ts +47 -2
- package/src/components/molecules/table/table.stories.tsx +75 -0
- package/src/components/molecules/table/table.tsx +140 -15
- package/src/components/organisms/data-table/ITDataTable.doc.mdx +25 -3
- package/src/components/organisms/data-table/ITDataTable.stories.tsx +98 -0
- package/src/components/organisms/data-table/dataTable.props.ts +8 -4
- package/src/components/organisms/data-table/dataTable.tsx +109 -8
- package/src/components/organisms/sidebar/sidebar.doc.mdx +39 -1
- 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/hooks/useTableState.ts +17 -11
- package/src/index.css +10 -0
- package/src/index.ts +5 -4
- package/src/showcases/DataShowcases.tsx +217 -21
- package/src/showcases/StructureShowcases.tsx +33 -5
- package/src/types/table.types.ts +17 -0
- 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
|
)}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useCallback, useMemo, useState } from "react";
|
|
2
|
+
import type { ColumnFilters, ColumnFilterValue } from "@/types/table.types";
|
|
2
3
|
|
|
3
4
|
interface SortConfig {
|
|
4
5
|
key: string;
|
|
@@ -13,30 +14,35 @@ export interface UseTableStateOptions {
|
|
|
13
14
|
export interface UseTableStateResult {
|
|
14
15
|
currentPage: number;
|
|
15
16
|
itemsPerPage: number;
|
|
16
|
-
filters:
|
|
17
|
+
filters: ColumnFilters;
|
|
17
18
|
sortConfig: SortConfig | null;
|
|
18
19
|
totalPages: number;
|
|
19
20
|
setTotalPages: (pages: number) => void;
|
|
20
21
|
goToPage: (page: number) => void;
|
|
21
22
|
handleItemsPerPageChange: (value: number) => void;
|
|
22
|
-
handleFilterChange: (
|
|
23
|
-
key: string,
|
|
24
|
-
value: string | boolean | number | undefined
|
|
25
|
-
) => void;
|
|
23
|
+
handleFilterChange: (key: string, value: ColumnFilterValue | undefined) => void;
|
|
26
24
|
handleSort: (key: string, sortable?: boolean) => void;
|
|
27
25
|
resetPage: () => void;
|
|
28
26
|
clearFilters: () => void;
|
|
29
27
|
}
|
|
30
28
|
|
|
29
|
+
/**
|
|
30
|
+
* A filter value is considered empty when it should be removed from the filter
|
|
31
|
+
* map: `undefined`/`null`/`""`, or a date range whose both ends are null.
|
|
32
|
+
*/
|
|
33
|
+
const isEmptyFilterValue = (value: ColumnFilterValue | undefined): boolean => {
|
|
34
|
+
if (value === undefined || value === null || value === "") return true;
|
|
35
|
+
if (Array.isArray(value)) return value[0] == null && value[1] == null;
|
|
36
|
+
return false;
|
|
37
|
+
};
|
|
38
|
+
|
|
31
39
|
export function useTableState({
|
|
32
40
|
defaultItemsPerPage = 10,
|
|
33
41
|
initialSort = null,
|
|
34
42
|
}: UseTableStateOptions = {}): UseTableStateResult {
|
|
35
43
|
const [currentPage, setCurrentPage] = useState(1);
|
|
36
44
|
const [itemsPerPage, setItemsPerPage] = useState(defaultItemsPerPage);
|
|
37
|
-
const [filters, setFilters] = useState<
|
|
38
|
-
Record<string, string | boolean | number>
|
|
39
|
-
>({});
|
|
45
|
+
const [filters, setFilters] = useState<ColumnFilters>({});
|
|
40
46
|
const [sortConfig, setSortConfig] = useState<SortConfig | null>(initialSort);
|
|
41
47
|
const [totalPages, setTotalPages] = useState(1);
|
|
42
48
|
|
|
@@ -58,14 +64,14 @@ export function useTableState({
|
|
|
58
64
|
}, []);
|
|
59
65
|
|
|
60
66
|
const handleFilterChange = useCallback(
|
|
61
|
-
(key: string, value:
|
|
67
|
+
(key: string, value: ColumnFilterValue | undefined) => {
|
|
62
68
|
setFilters((prev) => {
|
|
63
|
-
if (value
|
|
69
|
+
if (isEmptyFilterValue(value)) {
|
|
64
70
|
const next = { ...prev };
|
|
65
71
|
delete next[key];
|
|
66
72
|
return next;
|
|
67
73
|
}
|
|
68
|
-
return { ...prev, [key]: value };
|
|
74
|
+
return { ...prev, [key]: value as ColumnFilterValue };
|
|
69
75
|
});
|
|
70
76
|
setCurrentPage(1);
|
|
71
77
|
},
|
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
|
@@ -101,13 +101,13 @@ import type { ITSearchSelectProps } from "./components/molecules/search-select/s
|
|
|
101
101
|
import type { ITSelectProps } from "./components/molecules/select/select.props";
|
|
102
102
|
import type { ITSearchTableProps } from "./components/organisms/searchTable/searchTable.props";
|
|
103
103
|
import type { ITSegmentedControlProps } from "./components/atoms/segmented-control/segmented-control.props";
|
|
104
|
-
import type { ITSidebarProps, ITNavigationItem, ITNavigationSubItem } from "./components/organisms/sidebar/sidebar.props";
|
|
104
|
+
import type { ITSidebarProps, ITNavigationItem, ITNavigationSubItem, ITNavigationSubItemEntry, ITNavigationSubItemGroup } from "./components/organisms/sidebar/sidebar.props";
|
|
105
105
|
import type { ITSkeletonProps } from "./components/atoms/skeleton/skeleton.props";
|
|
106
106
|
import type { ITSlideToggleProps } from "./components/atoms/slide/slide.props";
|
|
107
107
|
import type { ITSliderProps } from "./components/atoms/slider/slider.props";
|
|
108
108
|
import type { ITStackProps } from "./components/atoms/stack/stack.props";
|
|
109
109
|
import type { ITStatCardProps } from "./components/molecules/stat-card/stat-card.props";
|
|
110
|
-
import type { ITTableProps, Column } from "./components/molecules/table/table.props";
|
|
110
|
+
import type { ITTableProps, Column, ColumnFilterMode } from "./components/molecules/table/table.props";
|
|
111
111
|
import type { ITTextProps } from "./components/atoms/text/text.props";
|
|
112
112
|
import type { ITTextareaProps } from "./components/atoms/textarea/textarea.props";
|
|
113
113
|
import type { ITTabsProps, ITTabItem } from "./components/molecules/tabs/tabs.props";
|
|
@@ -115,7 +115,7 @@ import type { ITTripleFilterProps, ITTripleFilterOption } from "./components/org
|
|
|
115
115
|
import type { ITToastProps } from "./components/organisms/toast/toast.props";
|
|
116
116
|
import type { ITWysiwygProps } from "./components/molecules/wysiwyg/wysiwyg.props";
|
|
117
117
|
import type { FieldConfig, FieldConfigV2 } from "./types/field.types";
|
|
118
|
-
import type { TableDensity } from "./types/table.types";
|
|
118
|
+
import type { ColumnFilters, ColumnFilterValue, TableDensity } from "./types/table.types";
|
|
119
119
|
|
|
120
120
|
// UTILS
|
|
121
121
|
import ITDropfile, { UploadStatus, FileTypeEnum } from "./components/organisms/dropfile/dropfile";
|
|
@@ -157,7 +157,7 @@ export {
|
|
|
157
157
|
};
|
|
158
158
|
|
|
159
159
|
export type {
|
|
160
|
-
Column, FieldConfig,
|
|
160
|
+
Column, ColumnFilters, ColumnFilterMode, ColumnFilterValue, FieldConfig,
|
|
161
161
|
FieldConfigV2,
|
|
162
162
|
ITAccordionItem, ITAccordionProps,
|
|
163
163
|
ITAlertProps, ITAvatarProps, ITBadgetProps,
|
|
@@ -173,6 +173,7 @@ export {
|
|
|
173
173
|
ITLayoutProps, ITLoaderProps, ITNavbarProps,
|
|
174
174
|
ITPageProps, ITPageHeaderProps,
|
|
175
175
|
ITNavigationItem, ITNavigationSubItem,
|
|
176
|
+
ITNavigationSubItemEntry, ITNavigationSubItemGroup,
|
|
176
177
|
ITPaginationProps, ITPopoverProps, ITProgressProps,
|
|
177
178
|
ITRadioGroupProps, ITRadioOption, ITSelectProps, ITSearchSelectProps,
|
|
178
179
|
ITSearchTableProps,
|