tekivex-ui 2.0.2 → 2.2.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.
Files changed (102) hide show
  1. package/dist/index.cjs +12 -12
  2. package/dist/index.d.ts +7 -2
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +2839 -2538
  5. package/dist/src/components/TkxAppBar.d.ts +12 -0
  6. package/dist/src/components/TkxAppBar.d.ts.map +1 -0
  7. package/dist/src/components/TkxAutocomplete.d.ts +25 -0
  8. package/dist/src/components/TkxAutocomplete.d.ts.map +1 -0
  9. package/dist/src/components/TkxBottomNav.d.ts +15 -0
  10. package/dist/src/components/TkxBottomNav.d.ts.map +1 -0
  11. package/dist/src/components/TkxBreadcrumb.d.ts +17 -0
  12. package/dist/src/components/TkxBreadcrumb.d.ts.map +1 -0
  13. package/dist/src/components/TkxCard.d.ts.map +1 -1
  14. package/dist/src/components/TkxConfigProvider.d.ts +100 -0
  15. package/dist/src/components/TkxConfigProvider.d.ts.map +1 -0
  16. package/dist/src/components/TkxDataGrid.d.ts +33 -0
  17. package/dist/src/components/TkxDataGrid.d.ts.map +1 -0
  18. package/dist/src/components/TkxEmpty.d.ts +870 -0
  19. package/dist/src/components/TkxEmpty.d.ts.map +1 -0
  20. package/dist/src/components/TkxForm.d.ts +54 -0
  21. package/dist/src/components/TkxForm.d.ts.map +1 -0
  22. package/dist/src/components/TkxLayout.d.ts +87 -0
  23. package/dist/src/components/TkxLayout.d.ts.map +1 -0
  24. package/dist/src/components/TkxMasonry.d.ts +12 -0
  25. package/dist/src/components/TkxMasonry.d.ts.map +1 -0
  26. package/dist/src/components/TkxModal.d.ts.map +1 -1
  27. package/dist/src/components/TkxPopover.d.ts +15 -0
  28. package/dist/src/components/TkxPopover.d.ts.map +1 -0
  29. package/dist/src/components/TkxRichTextDisplay.d.ts +19 -0
  30. package/dist/src/components/TkxRichTextDisplay.d.ts.map +1 -0
  31. package/dist/src/components/TkxSelect.d.ts.map +1 -1
  32. package/dist/src/components/TkxSlider.d.ts.map +1 -1
  33. package/dist/src/components/TkxSnackbar.d.ts +16 -0
  34. package/dist/src/components/TkxSnackbar.d.ts.map +1 -0
  35. package/dist/src/components/TkxSpeedDial.d.ts +18 -0
  36. package/dist/src/components/TkxSpeedDial.d.ts.map +1 -0
  37. package/dist/src/components/TkxSpin.d.ts +15 -0
  38. package/dist/src/components/TkxSpin.d.ts.map +1 -0
  39. package/dist/src/components/TkxStatistic.d.ts +1746 -0
  40. package/dist/src/components/TkxStatistic.d.ts.map +1 -0
  41. package/dist/src/components/TkxToolbar.d.ts +22 -0
  42. package/dist/src/components/TkxToolbar.d.ts.map +1 -0
  43. package/dist/src/components/TkxTooltip.d.ts.map +1 -1
  44. package/dist/src/components/TkxTransferList.d.ts +20 -0
  45. package/dist/src/components/TkxTransferList.d.ts.map +1 -0
  46. package/dist/src/components/TkxTreeView.d.ts +23 -0
  47. package/dist/src/components/TkxTreeView.d.ts.map +1 -0
  48. package/dist/src/components/TkxTypography.d.ts +2614 -0
  49. package/dist/src/components/TkxTypography.d.ts.map +1 -0
  50. package/dist/src/components/index.d.ts +20 -0
  51. package/dist/src/components/index.d.ts.map +1 -1
  52. package/dist/src/i18n/I18nProvider.d.ts +10 -0
  53. package/dist/src/i18n/I18nProvider.d.ts.map +1 -0
  54. package/dist/src/i18n/index.d.ts +50 -0
  55. package/dist/src/i18n/index.d.ts.map +1 -0
  56. package/dist/src/themes/index.d.ts +108 -0
  57. package/dist/src/themes/index.d.ts.map +1 -1
  58. package/package.json +2 -2
  59. package/src/components/TkxAppBar.tsx +260 -0
  60. package/src/components/TkxAutocomplete.tsx +453 -0
  61. package/src/components/TkxBottomNav.tsx +239 -0
  62. package/src/components/TkxBreadcrumb.tsx +322 -0
  63. package/src/components/TkxCard.tsx +3 -1
  64. package/src/components/TkxConfigProvider.tsx +458 -0
  65. package/src/components/TkxDataGrid.tsx +540 -0
  66. package/src/components/TkxEmpty.tsx +219 -0
  67. package/src/components/TkxForm.tsx +607 -0
  68. package/src/components/TkxLayout.tsx +647 -0
  69. package/src/components/TkxMasonry.tsx +176 -0
  70. package/src/components/TkxModal.tsx +4 -3
  71. package/src/components/TkxPopover.tsx +295 -0
  72. package/src/components/TkxRichTextDisplay.tsx +394 -0
  73. package/src/components/TkxSelect.tsx +2 -0
  74. package/src/components/TkxSlider.tsx +1 -0
  75. package/src/components/TkxSnackbar.tsx +314 -0
  76. package/src/components/TkxSpeedDial.tsx +395 -0
  77. package/src/components/TkxSpin.tsx +261 -0
  78. package/src/components/TkxStatistic.tsx +261 -0
  79. package/src/components/TkxToolbar.tsx +260 -0
  80. package/src/components/TkxTooltip.tsx +3 -2
  81. package/src/components/TkxTransferList.tsx +487 -0
  82. package/src/components/TkxTreeView.tsx +421 -0
  83. package/src/components/TkxTypography.tsx +263 -0
  84. package/src/components/index.ts +20 -0
  85. package/src/hooks/index.ts +1 -1
  86. package/src/i18n/I18nProvider.tsx +36 -0
  87. package/src/i18n/index.ts +237 -0
  88. package/src/themes/index.ts +149 -0
  89. package/dist/tests/TkxButton.test.d.ts +0 -2
  90. package/dist/tests/TkxButton.test.d.ts.map +0 -1
  91. package/dist/tests/a11y/components.test.d.ts +0 -2
  92. package/dist/tests/a11y/components.test.d.ts.map +0 -1
  93. package/dist/tests/quantum.test.d.ts +0 -2
  94. package/dist/tests/quantum.test.d.ts.map +0 -1
  95. package/dist/tests/security.test.d.ts +0 -2
  96. package/dist/tests/security.test.d.ts.map +0 -1
  97. package/dist/tests/setup.d.ts +0 -2
  98. package/dist/tests/setup.d.ts.map +0 -1
  99. package/dist/tests/tkx.test.d.ts +0 -2
  100. package/dist/tests/tkx.test.d.ts.map +0 -1
  101. package/dist/tests/wcag.test.d.ts +0 -2
  102. package/dist/tests/wcag.test.d.ts.map +0 -1
@@ -0,0 +1,260 @@
1
+ import {
2
+ useRef,
3
+ useCallback,
4
+ type ReactNode,
5
+ type CSSProperties,
6
+ type KeyboardEvent,
7
+ } from 'react';
8
+ import { useTheme } from '../themes';
9
+ import { sanitizeString } from '../engine/security';
10
+ import { useReducedMotion } from '../hooks';
11
+ import { tkx } from '../engine/tkx';
12
+
13
+ // ── Types ─────────────────────────────────────────────────────────────────────
14
+
15
+ export interface ToolbarItem {
16
+ id: string;
17
+ icon?: ReactNode;
18
+ label: string;
19
+ onClick?: () => void;
20
+ disabled?: boolean;
21
+ active?: boolean;
22
+ type?: 'button' | 'separator' | 'toggle';
23
+ }
24
+
25
+ export interface TkxToolbarProps {
26
+ items: ToolbarItem[];
27
+ orientation?: 'horizontal' | 'vertical';
28
+ size?: 'sm' | 'md' | 'lg';
29
+ variant?: 'default' | 'outlined' | 'filled';
30
+ ariaLabel?: string;
31
+ className?: string;
32
+ style?: CSSProperties;
33
+ }
34
+
35
+ // ── Size map ────────────────────────────────────────────────────────────────
36
+
37
+ interface SizeConfig {
38
+ buttonPx: number;
39
+ buttonPy: number;
40
+ fontSize: number;
41
+ iconSize: number;
42
+ gap: number;
43
+ separatorSize: number;
44
+ }
45
+
46
+ const SIZE_MAP: Record<'sm' | 'md' | 'lg', SizeConfig> = {
47
+ sm: { buttonPx: 6, buttonPy: 4, fontSize: 12, iconSize: 14, gap: 2, separatorSize: 16 },
48
+ md: { buttonPx: 8, buttonPy: 6, fontSize: 13, iconSize: 16, gap: 4, separatorSize: 20 },
49
+ lg: { buttonPx: 12, buttonPy: 8, fontSize: 14, iconSize: 18, gap: 6, separatorSize: 24 },
50
+ };
51
+
52
+ // ── Component ───────────────────────────────────────────────────────────────
53
+
54
+ export function TkxToolbar({
55
+ items,
56
+ orientation = 'horizontal',
57
+ size = 'md',
58
+ variant = 'default',
59
+ ariaLabel = 'Toolbar',
60
+ className,
61
+ style,
62
+ }: TkxToolbarProps) {
63
+ const theme = useTheme();
64
+ const reducedMotion = useReducedMotion();
65
+ const sizeConfig = SIZE_MAP[size];
66
+ const isHorizontal = orientation === 'horizontal';
67
+
68
+ const itemRefs = useRef<Map<string, HTMLElement>>(new Map());
69
+
70
+ // Collect focusable (non-separator, non-disabled) item ids
71
+ const focusableIds = items
72
+ .filter((item) => item.type !== 'separator' && !item.disabled)
73
+ .map((item) => item.id);
74
+
75
+ const focusItem = useCallback((id: string) => {
76
+ itemRefs.current.get(id)?.focus();
77
+ }, []);
78
+
79
+ // Roving tabindex keyboard handling
80
+ const handleKeyDown = useCallback(
81
+ (e: KeyboardEvent<HTMLElement>, itemId: string) => {
82
+ const currentIdx = focusableIds.indexOf(itemId);
83
+ if (currentIdx < 0) return;
84
+
85
+ const nextKey = isHorizontal ? 'ArrowRight' : 'ArrowDown';
86
+ const prevKey = isHorizontal ? 'ArrowLeft' : 'ArrowUp';
87
+
88
+ switch (e.key) {
89
+ case nextKey: {
90
+ e.preventDefault();
91
+ const next = currentIdx + 1 < focusableIds.length ? currentIdx + 1 : 0;
92
+ focusItem(focusableIds[next]);
93
+ break;
94
+ }
95
+ case prevKey: {
96
+ e.preventDefault();
97
+ const prev = currentIdx - 1 >= 0 ? currentIdx - 1 : focusableIds.length - 1;
98
+ focusItem(focusableIds[prev]);
99
+ break;
100
+ }
101
+ case 'Home': {
102
+ e.preventDefault();
103
+ focusItem(focusableIds[0]);
104
+ break;
105
+ }
106
+ case 'End': {
107
+ e.preventDefault();
108
+ focusItem(focusableIds[focusableIds.length - 1]);
109
+ break;
110
+ }
111
+ }
112
+ },
113
+ [focusableIds, focusItem, isHorizontal],
114
+ );
115
+
116
+ // Variant styles for the toolbar container
117
+ const containerVariantStyle = (): CSSProperties => {
118
+ switch (variant) {
119
+ case 'outlined':
120
+ return {
121
+ backgroundColor: 'transparent',
122
+ border: `1px solid ${theme.border}`,
123
+ };
124
+ case 'filled':
125
+ return {
126
+ backgroundColor: theme.surfaceAlt,
127
+ border: 'none',
128
+ };
129
+ default:
130
+ return {
131
+ backgroundColor: theme.surface,
132
+ border: `1px solid ${theme.border}`,
133
+ };
134
+ }
135
+ };
136
+
137
+ const getButtonStyle = (item: ToolbarItem): CSSProperties => {
138
+ const isToggle = item.type === 'toggle';
139
+ const isActive = item.active;
140
+
141
+ const base: CSSProperties = {
142
+ display: 'inline-flex',
143
+ alignItems: 'center',
144
+ justifyContent: 'center',
145
+ gap: 4,
146
+ padding: `${sizeConfig.buttonPy}px ${sizeConfig.buttonPx}px`,
147
+ fontSize: sizeConfig.fontSize,
148
+ borderRadius: 6,
149
+ border: 'none',
150
+ cursor: item.disabled ? 'not-allowed' : 'pointer',
151
+ backgroundColor: isActive ? `${theme.primary}20` : 'transparent',
152
+ color: isActive ? theme.primary : item.disabled ? theme.textMuted : theme.text,
153
+ opacity: item.disabled ? 0.5 : 1,
154
+ outline: 'none',
155
+ transition: reducedMotion ? 'none' : 'all 100ms ease',
156
+ whiteSpace: 'nowrap',
157
+ lineHeight: 1,
158
+ };
159
+
160
+ return base;
161
+ };
162
+
163
+ return (
164
+ <div
165
+ role="toolbar"
166
+ aria-label={ariaLabel}
167
+ aria-orientation={orientation}
168
+ className={tkx(
169
+ 'inline-flex items-center rounded-lg font-sans',
170
+ isHorizontal ? 'flex-row' : 'flex-col',
171
+ className ?? '',
172
+ )}
173
+ style={{
174
+ ...containerVariantStyle(),
175
+ padding: sizeConfig.gap,
176
+ gap: sizeConfig.gap,
177
+ ...style,
178
+ }}
179
+ >
180
+ {items.map((item, idx) => {
181
+ // Separator
182
+ if (item.type === 'separator') {
183
+ return (
184
+ <div
185
+ key={item.id}
186
+ role="separator"
187
+ aria-orientation={isHorizontal ? 'vertical' : 'horizontal'}
188
+ className={tkx('flex-shrink-0')}
189
+ style={{
190
+ ...(isHorizontal
191
+ ? { width: 1, height: sizeConfig.separatorSize, marginLeft: 2, marginRight: 2 }
192
+ : { height: 1, width: sizeConfig.separatorSize, marginTop: 2, marginBottom: 2 }),
193
+ backgroundColor: theme.border,
194
+ }}
195
+ />
196
+ );
197
+ }
198
+
199
+ const safeLabel = sanitizeString(item.label);
200
+ const isFocusable = !item.disabled;
201
+ const isFirstFocusable = focusableIds[0] === item.id;
202
+
203
+ return (
204
+ <button
205
+ key={item.id}
206
+ ref={(el) => {
207
+ if (el) itemRefs.current.set(item.id, el);
208
+ else itemRefs.current.delete(item.id);
209
+ }}
210
+ type="button"
211
+ role={item.type === 'toggle' ? undefined : undefined}
212
+ aria-pressed={item.type === 'toggle' ? item.active : undefined}
213
+ aria-label={safeLabel}
214
+ aria-disabled={item.disabled || undefined}
215
+ tabIndex={isFocusable && isFirstFocusable ? 0 : -1}
216
+ disabled={item.disabled}
217
+ onClick={() => {
218
+ if (!item.disabled) item.onClick?.();
219
+ }}
220
+ onKeyDown={(e) => {
221
+ handleKeyDown(e, item.id);
222
+ if (e.key === 'Enter' || e.key === ' ') {
223
+ e.preventDefault();
224
+ if (!item.disabled) item.onClick?.();
225
+ }
226
+ }}
227
+ className={tkx(
228
+ 'focus-visible:ring-2 focus-visible:ring-offset-1',
229
+ )}
230
+ style={getButtonStyle(item)}
231
+ onMouseEnter={(e) => {
232
+ if (!item.disabled && !item.active) {
233
+ (e.currentTarget as HTMLElement).style.backgroundColor = theme.surfaceAlt;
234
+ }
235
+ }}
236
+ onMouseLeave={(e) => {
237
+ if (!item.disabled) {
238
+ (e.currentTarget as HTMLElement).style.backgroundColor = item.active
239
+ ? `${theme.primary}20`
240
+ : 'transparent';
241
+ }
242
+ }}
243
+ >
244
+ {item.icon && (
245
+ <span
246
+ aria-hidden="true"
247
+ style={{ width: sizeConfig.iconSize, height: sizeConfig.iconSize, display: 'flex', alignItems: 'center', justifyContent: 'center' }}
248
+ >
249
+ {item.icon}
250
+ </span>
251
+ )}
252
+ {!item.icon && safeLabel}
253
+ </button>
254
+ );
255
+ })}
256
+ </div>
257
+ );
258
+ }
259
+
260
+ export default TkxToolbar;
@@ -1,7 +1,7 @@
1
1
  import { useState, useRef, useId, type ReactElement, cloneElement } from 'react';
2
2
  import { useTheme } from '../themes';
3
3
  import { sanitizeString } from '../engine/security';
4
- import { useEscapeKey, useClickOutside } from '../hooks';
4
+ import { useEscapeKey, useClickOutside, useReducedMotion } from '../hooks';
5
5
  import { getAccessibleForeground } from '../engine/wcag';
6
6
  import { tkx } from '../engine/tkx';
7
7
 
@@ -27,6 +27,7 @@ export function TkxTooltip({ content, children, placement = 'top', delay = 300 }
27
27
  const wrapperRef = useRef<HTMLSpanElement>(null);
28
28
  const tooltipId = useId();
29
29
  const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
30
+ const reducedMotion = useReducedMotion();
30
31
  const safeContent = sanitizeString(content);
31
32
  const textColor = getAccessibleForeground(theme.surfaceAlt, [theme.text, '#ffffff', '#000000']);
32
33
 
@@ -51,7 +52,7 @@ export function TkxTooltip({ content, children, placement = 'top', delay = 300 }
51
52
  <span
52
53
  id={tooltipId}
53
54
  role="tooltip"
54
- className={tkx('absolute z-[9000] text-xs font-sans py-1.5 px-2.5 rounded-md whitespace-nowrap pointer-events-none animate-fade-in')}
55
+ className={tkx('absolute z-[9000] text-xs font-sans py-1.5 px-2.5 rounded-md whitespace-nowrap pointer-events-none', !reducedMotion && 'animate-fade-in')}
55
56
  style={{
56
57
  backgroundColor: theme.surfaceAlt,
57
58
  color: textColor,