@umituz/web-design-system 4.1.0 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/__tests__/components.smoke.test.tsx +227 -0
- package/src/presentation/atoms/Progress.tsx +11 -2
- package/src/presentation/organisms/DataTable.tsx +345 -127
- package/src/presentation/organisms/EmptyState.tsx +56 -26
- package/src/presentation/organisms/Tabs.tsx +61 -9
- package/src/presentation/organisms/index.ts +10 -2
- package/src/styles/compile.css +37 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @description Tabbed interface
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { useState, useCallback, type ReactNode, type HTMLAttributes } from 'react';
|
|
6
|
+
import { useRef, useState, useCallback, type KeyboardEvent, type ReactNode, type HTMLAttributes } from 'react';
|
|
7
7
|
import { cn } from '../../infrastructure/utils';
|
|
8
8
|
import type { BaseProps } from '../../domain/types';
|
|
9
9
|
|
|
@@ -12,12 +12,17 @@ export interface Tab {
|
|
|
12
12
|
label: string;
|
|
13
13
|
content?: ReactNode;
|
|
14
14
|
disabled?: boolean;
|
|
15
|
+
count?: number;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export interface TabsProps extends HTMLAttributes<HTMLDivElement>, BaseProps {
|
|
18
19
|
tabs: Tab[];
|
|
19
20
|
defaultValue?: string;
|
|
21
|
+
value?: string;
|
|
22
|
+
onValueChange?: (value: string) => void;
|
|
20
23
|
variant?: 'default' | 'pills' | 'underline';
|
|
24
|
+
ariaLabel?: string;
|
|
25
|
+
panelId?: string;
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
const variantStyles: Record<'default' | 'pills' | 'underline', string> = {
|
|
@@ -41,15 +46,48 @@ const tabVariantStyles: Record<'default' | 'pills' | 'underline', { base: string
|
|
|
41
46
|
},
|
|
42
47
|
};
|
|
43
48
|
|
|
44
|
-
export function Tabs({ tabs, defaultValue, variant = 'default', className, ...props }: TabsProps) {
|
|
45
|
-
const
|
|
49
|
+
export function Tabs({ tabs, defaultValue, value, onValueChange, variant = 'default', ariaLabel, panelId, className, ...props }: TabsProps) {
|
|
50
|
+
const listRef = useRef<HTMLDivElement>(null);
|
|
51
|
+
const [internalTab, setInternalTab] = useState(defaultValue || tabs[0]?.value);
|
|
52
|
+
const activeTab = value ?? internalTab;
|
|
46
53
|
|
|
47
|
-
const handleTabChange = useCallback(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
const handleTabChange = useCallback(
|
|
55
|
+
(next: string) => {
|
|
56
|
+
const tab = tabs.find((t) => t.value === next);
|
|
57
|
+
if (tab && !tab.disabled) {
|
|
58
|
+
setInternalTab(next);
|
|
59
|
+
onValueChange?.(next);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
[tabs, onValueChange]
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const moveFocus = useCallback(
|
|
66
|
+
(direction: 1 | -1) => {
|
|
67
|
+
const buttons = Array.from(listRef.current?.querySelectorAll<HTMLButtonElement>('[role="tab"]:not([disabled])') ?? []);
|
|
68
|
+
if (buttons.length === 0) return;
|
|
69
|
+
const currentIndex = buttons.findIndex((button) => button.getAttribute('data-state') === 'active');
|
|
70
|
+
const next = buttons[(currentIndex + direction + buttons.length) % buttons.length];
|
|
71
|
+
if (next) {
|
|
72
|
+
next.focus();
|
|
73
|
+
next.click();
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
[]
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const handleKeyDown = useCallback(
|
|
80
|
+
(event: KeyboardEvent<HTMLDivElement>) => {
|
|
81
|
+
if (event.key === 'ArrowRight') {
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
moveFocus(1);
|
|
84
|
+
} else if (event.key === 'ArrowLeft') {
|
|
85
|
+
event.preventDefault();
|
|
86
|
+
moveFocus(-1);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
[moveFocus]
|
|
90
|
+
);
|
|
53
91
|
|
|
54
92
|
const currentTab = tabs.find((t) => t.value === activeTab);
|
|
55
93
|
|
|
@@ -57,7 +95,10 @@ export function Tabs({ tabs, defaultValue, variant = 'default', className, ...pr
|
|
|
57
95
|
<div className={cn('w-full', className)} {...props}>
|
|
58
96
|
{/* Tab List */}
|
|
59
97
|
<div
|
|
98
|
+
ref={listRef}
|
|
60
99
|
role="tablist"
|
|
100
|
+
aria-label={ariaLabel}
|
|
101
|
+
onKeyDown={handleKeyDown}
|
|
61
102
|
className={cn('inline-flex w-full', variantStyles[variant])}
|
|
62
103
|
>
|
|
63
104
|
{tabs.map((tab) => {
|
|
@@ -67,11 +108,16 @@ export function Tabs({ tabs, defaultValue, variant = 'default', className, ...pr
|
|
|
67
108
|
return (
|
|
68
109
|
<button
|
|
69
110
|
key={tab.value}
|
|
111
|
+
type="button"
|
|
70
112
|
role="tab"
|
|
71
113
|
aria-selected={isActive}
|
|
114
|
+
data-state={isActive ? 'active' : 'inactive'}
|
|
115
|
+
aria-controls={panelId}
|
|
116
|
+
tabIndex={isActive ? 0 : -1}
|
|
72
117
|
disabled={tab.disabled}
|
|
73
118
|
onClick={() => handleTabChange(tab.value)}
|
|
74
119
|
className={cn(
|
|
120
|
+
'inline-flex items-center gap-1.5',
|
|
75
121
|
styles.base,
|
|
76
122
|
isActive
|
|
77
123
|
? styles.active
|
|
@@ -80,6 +126,11 @@ export function Tabs({ tabs, defaultValue, variant = 'default', className, ...pr
|
|
|
80
126
|
)}
|
|
81
127
|
>
|
|
82
128
|
{tab.label}
|
|
129
|
+
{typeof tab.count === 'number' && (
|
|
130
|
+
<span className="rounded-full bg-muted px-1.5 text-xs leading-4 text-muted-foreground">
|
|
131
|
+
{tab.count}
|
|
132
|
+
</span>
|
|
133
|
+
)}
|
|
83
134
|
</button>
|
|
84
135
|
);
|
|
85
136
|
})}
|
|
@@ -88,6 +139,7 @@ export function Tabs({ tabs, defaultValue, variant = 'default', className, ...pr
|
|
|
88
139
|
{/* Tab Content */}
|
|
89
140
|
<div
|
|
90
141
|
role="tabpanel"
|
|
142
|
+
id={panelId}
|
|
91
143
|
className="mt-4"
|
|
92
144
|
>
|
|
93
145
|
{currentTab?.content}
|
|
@@ -71,8 +71,16 @@ export type { EmptyStateProps } from './EmptyState';
|
|
|
71
71
|
export { LoadingState } from './LoadingState';
|
|
72
72
|
export type { LoadingStateProps } from './LoadingState';
|
|
73
73
|
|
|
74
|
-
export { DataTable } from './DataTable';
|
|
75
|
-
export type {
|
|
74
|
+
export { DataTable, TablePagination } from './DataTable';
|
|
75
|
+
export type {
|
|
76
|
+
DataTableProps,
|
|
77
|
+
Column,
|
|
78
|
+
SortState,
|
|
79
|
+
TablePaginationProps,
|
|
80
|
+
ColumnAlign,
|
|
81
|
+
ColumnHideBelow,
|
|
82
|
+
SortDirection,
|
|
83
|
+
} from './DataTable';
|
|
76
84
|
|
|
77
85
|
export { StatCard } from './StatCard';
|
|
78
86
|
export type { StatCardProps } from './StatCard';
|
package/src/styles/compile.css
CHANGED
|
@@ -16,3 +16,40 @@
|
|
|
16
16
|
@import "tw-animate-css";
|
|
17
17
|
@import "./theme.css";
|
|
18
18
|
@source "../src";
|
|
19
|
+
|
|
20
|
+
@media (max-width: 767.98px) {
|
|
21
|
+
.ds-datatable-stack table,
|
|
22
|
+
.ds-datatable-stack tbody,
|
|
23
|
+
.ds-datatable-stack tr,
|
|
24
|
+
.ds-datatable-stack td {
|
|
25
|
+
display: block;
|
|
26
|
+
width: 100%;
|
|
27
|
+
}
|
|
28
|
+
.ds-datatable-stack thead {
|
|
29
|
+
display: none;
|
|
30
|
+
}
|
|
31
|
+
.ds-datatable-stack tr {
|
|
32
|
+
border: 1px solid var(--color-border);
|
|
33
|
+
border-radius: 0.5rem;
|
|
34
|
+
margin-bottom: 0.625rem;
|
|
35
|
+
padding: 0.25rem 0.75rem 0.375rem;
|
|
36
|
+
}
|
|
37
|
+
.ds-datatable-stack tr:last-child {
|
|
38
|
+
margin-bottom: 0;
|
|
39
|
+
}
|
|
40
|
+
.ds-datatable-stack td:not(.hidden) {
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: baseline;
|
|
43
|
+
justify-content: space-between;
|
|
44
|
+
gap: 1rem;
|
|
45
|
+
padding: 0.375rem 0;
|
|
46
|
+
border-bottom: 1px solid var(--color-border);
|
|
47
|
+
text-align: right;
|
|
48
|
+
}
|
|
49
|
+
.ds-datatable-stack td:not(.hidden)::before {
|
|
50
|
+
content: attr(data-label);
|
|
51
|
+
font-weight: 500;
|
|
52
|
+
color: var(--color-muted-foreground);
|
|
53
|
+
text-align: left;
|
|
54
|
+
}
|
|
55
|
+
}
|