@abstraks-dev/ui-library 1.0.1
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/LICENSE +21 -0
- package/README.md +708 -0
- package/dist/__tests__/Anchor.test.js +145 -0
- package/dist/__tests__/ArrowRight.test.js +91 -0
- package/dist/__tests__/Avatar.test.js +123 -0
- package/dist/__tests__/Button.test.js +82 -0
- package/dist/__tests__/Card.test.js +198 -0
- package/dist/__tests__/CheckCircle.test.js +98 -0
- package/dist/__tests__/Checkbox.test.js +161 -0
- package/dist/__tests__/ChevronDown.test.js +73 -0
- package/dist/__tests__/Close.test.js +98 -0
- package/dist/__tests__/EditSquare.test.js +99 -0
- package/dist/__tests__/Error.test.js +74 -0
- package/dist/__tests__/Footer.test.js +66 -0
- package/dist/__tests__/Heading.test.js +227 -0
- package/dist/__tests__/Hero.test.js +74 -0
- package/dist/__tests__/Label.test.js +123 -0
- package/dist/__tests__/Loader.test.js +115 -0
- package/dist/__tests__/MenuHover.test.js +137 -0
- package/dist/__tests__/Paragraph.test.js +93 -0
- package/dist/__tests__/PlusCircle.test.js +99 -0
- package/dist/__tests__/Radio.test.js +153 -0
- package/dist/__tests__/Select.test.js +187 -0
- package/dist/__tests__/Tabs.test.js +162 -0
- package/dist/__tests__/TextArea.test.js +127 -0
- package/dist/__tests__/TextInput.test.js +181 -0
- package/dist/__tests__/Toggle.test.js +120 -0
- package/dist/__tests__/TrashX.test.js +99 -0
- package/dist/__tests__/useHeadingAccessibility.test.js +144 -0
- package/dist/components/Anchor.js +131 -0
- package/dist/components/Animation.js +129 -0
- package/dist/components/AnimationGroup.js +207 -0
- package/dist/components/AnimationToggle.js +216 -0
- package/dist/components/Avatar.js +153 -0
- package/dist/components/Button.js +218 -0
- package/dist/components/Card.js +222 -0
- package/dist/components/Checkbox.js +305 -0
- package/dist/components/Crud.js +564 -0
- package/dist/components/DragAndDrop.js +337 -0
- package/dist/components/Error.js +206 -0
- package/dist/components/Footer.js +99 -0
- package/dist/components/Form.js +412 -0
- package/dist/components/Header.js +372 -0
- package/dist/components/Heading.js +134 -0
- package/dist/components/Hero.js +181 -0
- package/dist/components/Label.js +256 -0
- package/dist/components/Loader.js +302 -0
- package/dist/components/MenuHover.js +114 -0
- package/dist/components/Paragraph.js +128 -0
- package/dist/components/Prompt.js +61 -0
- package/dist/components/Radio.js +254 -0
- package/dist/components/Select.js +422 -0
- package/dist/components/SideMenu.js +313 -0
- package/dist/components/Tabs.js +297 -0
- package/dist/components/TextArea.js +370 -0
- package/dist/components/TextInput.js +286 -0
- package/dist/components/Toggle.js +186 -0
- package/dist/components/crudFiles/CrudEditBase.js +150 -0
- package/dist/components/crudFiles/CrudViewBase.js +39 -0
- package/dist/components/crudFiles/crudDevelopment.js +118 -0
- package/dist/components/crudFiles/crudEditHandlers.js +50 -0
- package/dist/constants/animation.js +30 -0
- package/dist/icons/ArrowIcon.js +32 -0
- package/dist/icons/ArrowRight.js +33 -0
- package/dist/icons/CheckCircle.js +33 -0
- package/dist/icons/ChevronDown.js +28 -0
- package/dist/icons/Close.js +33 -0
- package/dist/icons/EditSquare.js +33 -0
- package/dist/icons/Ellipses.js +34 -0
- package/dist/icons/Hamburger.js +39 -0
- package/dist/icons/LoadingSpinner.js +42 -0
- package/dist/icons/PlusCircle.js +33 -0
- package/dist/icons/SaveIcon.js +32 -0
- package/dist/icons/TrashX.js +33 -0
- package/dist/icons/__tests__/CheckCircle.test.js +9 -0
- package/dist/icons/__tests__/ChevronDown.test.js +9 -0
- package/dist/icons/__tests__/Close.test.js +9 -0
- package/dist/icons/__tests__/EditSquare.test.js +9 -0
- package/dist/icons/__tests__/PlusCircle.test.js +9 -0
- package/dist/icons/__tests__/TrashX.test.js +9 -0
- package/dist/icons/index.js +89 -0
- package/dist/index.js +332 -0
- package/dist/setupTests.js +3 -0
- package/dist/styles/_variables.scss +286 -0
- package/dist/styles/anchor.scss +40 -0
- package/dist/styles/animation-accessibility.scss +96 -0
- package/dist/styles/animation-toggle.scss +233 -0
- package/dist/styles/animation.scss +3781 -0
- package/dist/styles/avatar.scss +285 -0
- package/dist/styles/button.scss +430 -0
- package/dist/styles/card.scss +210 -0
- package/dist/styles/checkbox.scss +160 -0
- package/dist/styles/crud.scss +474 -0
- package/dist/styles/dragAndDrop.scss +312 -0
- package/dist/styles/error.scss +232 -0
- package/dist/styles/footer.scss +58 -0
- package/dist/styles/form.scss +420 -0
- package/dist/styles/grid.scss +29 -0
- package/dist/styles/header.scss +276 -0
- package/dist/styles/heading.scss +118 -0
- package/dist/styles/hero.scss +185 -0
- package/dist/styles/htmlElements.scss +20 -0
- package/dist/styles/image.scss +9 -0
- package/dist/styles/label.scss +340 -0
- package/dist/styles/list-item.scss +5 -0
- package/dist/styles/loader.scss +354 -0
- package/dist/styles/logo.scss +19 -0
- package/dist/styles/main.css +9056 -0
- package/dist/styles/main.css.map +1 -0
- package/dist/styles/main.scss +0 -0
- package/dist/styles/menu-hover.scss +30 -0
- package/dist/styles/paragraph.scss +88 -0
- package/dist/styles/prompt.scss +51 -0
- package/dist/styles/radio.scss +202 -0
- package/dist/styles/select.scss +363 -0
- package/dist/styles/side-menu.scss +334 -0
- package/dist/styles/tabs.scss +540 -0
- package/dist/styles/text-area.scss +388 -0
- package/dist/styles/text-input.scss +171 -0
- package/dist/styles/toggle.scss +0 -0
- package/dist/styles/unordered-list.scss +8 -0
- package/dist/utils/ScrollHandler.js +30 -0
- package/dist/utils/accessibility.js +128 -0
- package/dist/utils/heroUtils.js +316 -0
- package/dist/utils/index.js +104 -0
- package/dist/utils/inputValidation.js +29 -0
- package/dist/utils/keyboardNavigation.js +536 -0
- package/dist/utils/labelUtils.js +708 -0
- package/dist/utils/loaderUtils.js +387 -0
- package/dist/utils/menuUtils.js +575 -0
- package/dist/utils/useHeadingAccessibility.js +298 -0
- package/dist/utils/useRadioGroup.js +260 -0
- package/dist/utils/useSelectAccessibility.js +426 -0
- package/dist/utils/useTabsAccessibility.js +278 -0
- package/dist/utils/useTextAreaAccessibility.js +255 -0
- package/dist/utils/useTextInputAccessibility.js +295 -0
- package/dist/utils/useTypographyAccessibility.js +168 -0
- package/dist/utils/useWindowSize.js +32 -0
- package/dist/utils/utils/ScrollHandler.js +26 -0
- package/dist/utils/utils/accessibility.js +133 -0
- package/dist/utils/utils/heroUtils.js +348 -0
- package/dist/utils/utils/index.js +9 -0
- package/dist/utils/utils/inputValidation.js +22 -0
- package/dist/utils/utils/keyboardNavigation.js +664 -0
- package/dist/utils/utils/labelUtils.js +772 -0
- package/dist/utils/utils/loaderUtils.js +436 -0
- package/dist/utils/utils/menuUtils.js +651 -0
- package/dist/utils/utils/useHeadingAccessibility.js +334 -0
- package/dist/utils/utils/useRadioGroup.js +311 -0
- package/dist/utils/utils/useSelectAccessibility.js +498 -0
- package/dist/utils/utils/useTabsAccessibility.js +316 -0
- package/dist/utils/utils/useTextAreaAccessibility.js +303 -0
- package/dist/utils/utils/useTextInputAccessibility.js +338 -0
- package/dist/utils/utils/useTypographyAccessibility.js +180 -0
- package/dist/utils/utils/useWindowSize.js +26 -0
- package/dist/utils/utils/validation.js +131 -0
- package/dist/utils/validation.js +139 -0
- package/package.json +90 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.SideMenu = void 0;
|
|
7
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
8
|
+
var _propTypes = require("prop-types");
|
|
9
|
+
var _Heading = require("./Heading");
|
|
10
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
11
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
12
|
+
/**
|
|
13
|
+
* SideMenu Component
|
|
14
|
+
*
|
|
15
|
+
* A fully accessible, modern side menu component following WCAG 2.1 AA guidelines.
|
|
16
|
+
* Supports multiple directions (horizontal/vertical), keyboard navigation, and focus management.
|
|
17
|
+
*
|
|
18
|
+
* This is a stateless component - state management should be handled externally.
|
|
19
|
+
*
|
|
20
|
+
* @component
|
|
21
|
+
* @example
|
|
22
|
+
* // Basic horizontal side menu
|
|
23
|
+
* <SideMenu
|
|
24
|
+
* id="main-menu"
|
|
25
|
+
* direction="horizontal"
|
|
26
|
+
* isOpen={isMenuOpen}
|
|
27
|
+
* onClose={handleMenuClose}
|
|
28
|
+
* onOpen={handleMenuOpen}
|
|
29
|
+
* trigger={<Button>Open Menu</Button>}
|
|
30
|
+
* title="Navigation Menu"
|
|
31
|
+
* >
|
|
32
|
+
* <nav>Menu content here</nav>
|
|
33
|
+
* </SideMenu>
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* // Vertical menu with custom positioning
|
|
37
|
+
* <SideMenu
|
|
38
|
+
* id="top-menu"
|
|
39
|
+
* direction="vertical"
|
|
40
|
+
* position="top"
|
|
41
|
+
* isOpen={isTopMenuOpen}
|
|
42
|
+
* onToggle={handleTopMenuToggle}
|
|
43
|
+
* closeOnBackdrop={false}
|
|
44
|
+
* trigger={<Button>Toggle Top Menu</Button>}
|
|
45
|
+
* >
|
|
46
|
+
* <div>Top menu content</div>
|
|
47
|
+
* </SideMenu>
|
|
48
|
+
*/
|
|
49
|
+
const SideMenu = exports.SideMenu = /*#__PURE__*/(0, _react.forwardRef)(({
|
|
50
|
+
// Core props
|
|
51
|
+
id,
|
|
52
|
+
direction = 'horizontal',
|
|
53
|
+
position = 'default',
|
|
54
|
+
isOpen = false,
|
|
55
|
+
onToggle = () => {},
|
|
56
|
+
onClose = () => {},
|
|
57
|
+
onOpen = () => {},
|
|
58
|
+
// Content props
|
|
59
|
+
children = null,
|
|
60
|
+
title = null,
|
|
61
|
+
trigger = null,
|
|
62
|
+
leftIconClose = null,
|
|
63
|
+
rightIconClose = null,
|
|
64
|
+
// Behavior props
|
|
65
|
+
closeOnBackdrop = true,
|
|
66
|
+
closeOnEscape = true,
|
|
67
|
+
trapFocus = true,
|
|
68
|
+
// Layout props
|
|
69
|
+
className = '',
|
|
70
|
+
variant = 'default',
|
|
71
|
+
size = 'medium',
|
|
72
|
+
// Accessibility props
|
|
73
|
+
'aria-label': ariaLabel,
|
|
74
|
+
'aria-labelledby': ariaLabelledby,
|
|
75
|
+
'aria-describedby': ariaDescribedby,
|
|
76
|
+
// Legacy props (for backward compatibility - internal use only)
|
|
77
|
+
componentName = 'side-menu',
|
|
78
|
+
additionalClassName = '',
|
|
79
|
+
testId,
|
|
80
|
+
...restProps
|
|
81
|
+
}, ref) => {
|
|
82
|
+
// Handle legacy prop mapping
|
|
83
|
+
const finalId = id || testId || `side-menu-${Math.random().toString(36).substr(2, 9)}`;
|
|
84
|
+
const finalClassName = className || additionalClassName;
|
|
85
|
+
const handleToggle = (0, _react.useCallback)(() => {
|
|
86
|
+
if (isOpen) {
|
|
87
|
+
onClose();
|
|
88
|
+
} else {
|
|
89
|
+
onOpen();
|
|
90
|
+
}
|
|
91
|
+
onToggle();
|
|
92
|
+
}, [isOpen, onClose, onOpen, onToggle]);
|
|
93
|
+
const handleBackdropClick = (0, _react.useCallback)(() => {
|
|
94
|
+
if (closeOnBackdrop && isOpen) {
|
|
95
|
+
onClose();
|
|
96
|
+
onToggle();
|
|
97
|
+
}
|
|
98
|
+
}, [closeOnBackdrop, isOpen, onClose, onToggle]);
|
|
99
|
+
const handleEscapeKey = (0, _react.useCallback)(event => {
|
|
100
|
+
if (closeOnEscape && event.key === 'Escape' && isOpen) {
|
|
101
|
+
onClose();
|
|
102
|
+
onToggle();
|
|
103
|
+
}
|
|
104
|
+
}, [closeOnEscape, isOpen, onClose, onToggle]);
|
|
105
|
+
|
|
106
|
+
// Build CSS classes
|
|
107
|
+
const wrapperClasses = [componentName, finalClassName, `${componentName}--${variant}`, `${componentName}--${size}`, `${componentName}--${direction}`, position !== 'default' && `${componentName}--${position}`, isOpen && `${componentName}--open`].filter(Boolean).join(' ');
|
|
108
|
+
const getMenuClassNames = () => {
|
|
109
|
+
const classes = ['menu'];
|
|
110
|
+
|
|
111
|
+
// Add direction class
|
|
112
|
+
if (direction === 'horizontal') {
|
|
113
|
+
classes.push('menu-x');
|
|
114
|
+
// Add position class for horizontal menus (left/right)
|
|
115
|
+
if (position === 'left') classes.push('menu-x-left');
|
|
116
|
+
} else if (direction === 'vertical') {
|
|
117
|
+
classes.push('menu-y');
|
|
118
|
+
// Add position class for vertical menus (top/bottom)
|
|
119
|
+
if (position === 'bottom') classes.push('menu-y-bottom');
|
|
120
|
+
}
|
|
121
|
+
if (isOpen) classes.push('active');
|
|
122
|
+
return classes.join(' ');
|
|
123
|
+
};
|
|
124
|
+
const getOverlayClassNames = () => {
|
|
125
|
+
const classes = ['overlay'];
|
|
126
|
+
if (isOpen) classes.push('active');
|
|
127
|
+
return classes.join(' ');
|
|
128
|
+
};
|
|
129
|
+
const getDefaultAriaLabel = () => {
|
|
130
|
+
const directionLabel = direction === 'horizontal' ? 'side' : 'top';
|
|
131
|
+
return `Open ${directionLabel} menu`;
|
|
132
|
+
};
|
|
133
|
+
return /*#__PURE__*/_react.default.createElement("div", _extends({
|
|
134
|
+
ref: ref,
|
|
135
|
+
className: wrapperClasses,
|
|
136
|
+
"data-testid": testId || componentName,
|
|
137
|
+
onKeyDown: handleEscapeKey
|
|
138
|
+
}, restProps), /*#__PURE__*/_react.default.createElement("div", {
|
|
139
|
+
className: "trigger",
|
|
140
|
+
"data-testid": "trigger",
|
|
141
|
+
onClick: handleToggle,
|
|
142
|
+
role: "button",
|
|
143
|
+
tabIndex: 0,
|
|
144
|
+
onKeyDown: e => {
|
|
145
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
146
|
+
e.preventDefault();
|
|
147
|
+
handleToggle();
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
"aria-expanded": isOpen,
|
|
151
|
+
"aria-controls": `${finalId}-menu`,
|
|
152
|
+
"aria-label": ariaLabel || getDefaultAriaLabel(),
|
|
153
|
+
"aria-labelledby": !ariaLabel ? ariaLabelledby : undefined,
|
|
154
|
+
"aria-describedby": ariaDescribedby
|
|
155
|
+
}, trigger), /*#__PURE__*/_react.default.createElement("div", {
|
|
156
|
+
className: getOverlayClassNames(),
|
|
157
|
+
"data-testid": `overlay${isOpen ? ' active' : ''}`,
|
|
158
|
+
onClick: handleBackdropClick,
|
|
159
|
+
"aria-hidden": "true"
|
|
160
|
+
}), /*#__PURE__*/_react.default.createElement("div", {
|
|
161
|
+
id: `${finalId}-menu`,
|
|
162
|
+
className: getMenuClassNames(),
|
|
163
|
+
"data-testid": `menu menu-${direction === 'horizontal' ? 'x' : 'y'}${isOpen ? ' active' : ''}`,
|
|
164
|
+
role: "dialog",
|
|
165
|
+
"aria-modal": "true",
|
|
166
|
+
"aria-labelledby": title ? `${finalId}-title` : ariaLabelledby,
|
|
167
|
+
"aria-label": !title && !ariaLabelledby ? ariaLabel || 'Side menu' : undefined,
|
|
168
|
+
"aria-describedby": ariaDescribedby,
|
|
169
|
+
tabIndex: -1
|
|
170
|
+
}, /*#__PURE__*/_react.default.createElement("div", {
|
|
171
|
+
className: "wrapper-header"
|
|
172
|
+
}, leftIconClose && /*#__PURE__*/_react.default.createElement("button", {
|
|
173
|
+
className: "icon-left",
|
|
174
|
+
"data-testid": "icon-left",
|
|
175
|
+
onClick: handleToggle,
|
|
176
|
+
"aria-label": "Close menu",
|
|
177
|
+
type: "button"
|
|
178
|
+
}, leftIconClose), title && /*#__PURE__*/_react.default.createElement(_Heading.Heading, {
|
|
179
|
+
variant: 2,
|
|
180
|
+
id: `${finalId}-title`,
|
|
181
|
+
className: "menu-title"
|
|
182
|
+
}, title), rightIconClose && /*#__PURE__*/_react.default.createElement("button", {
|
|
183
|
+
className: "icon-right",
|
|
184
|
+
"data-testid": "icon-right",
|
|
185
|
+
onClick: handleToggle,
|
|
186
|
+
"aria-label": "Close menu",
|
|
187
|
+
type: "button"
|
|
188
|
+
}, rightIconClose)), /*#__PURE__*/_react.default.createElement("div", {
|
|
189
|
+
className: "wrapper-content",
|
|
190
|
+
role: "document"
|
|
191
|
+
}, children)));
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Set display name for debugging
|
|
195
|
+
SideMenu.displayName = 'SideMenu';
|
|
196
|
+
SideMenu.propTypes = {
|
|
197
|
+
// ===========================================
|
|
198
|
+
// Core Props
|
|
199
|
+
// ===========================================
|
|
200
|
+
/**
|
|
201
|
+
* Unique identifier for the side menu
|
|
202
|
+
*/
|
|
203
|
+
id: _propTypes.string,
|
|
204
|
+
/**
|
|
205
|
+
* Direction of the menu (horizontal or vertical)
|
|
206
|
+
*/
|
|
207
|
+
direction: (0, _propTypes.oneOf)(['horizontal', 'vertical']),
|
|
208
|
+
/**
|
|
209
|
+
* Position of the menu (default, left, right, top, bottom)
|
|
210
|
+
*/
|
|
211
|
+
position: (0, _propTypes.oneOf)(['default', 'left', 'right', 'top', 'bottom']),
|
|
212
|
+
// ===========================================
|
|
213
|
+
// Content Props
|
|
214
|
+
// ===========================================
|
|
215
|
+
/**
|
|
216
|
+
* Content to display inside the menu
|
|
217
|
+
*/
|
|
218
|
+
children: (0, _propTypes.oneOfType)([_propTypes.node, _propTypes.array]),
|
|
219
|
+
/**
|
|
220
|
+
* Title of the menu
|
|
221
|
+
*/
|
|
222
|
+
title: _propTypes.node,
|
|
223
|
+
/**
|
|
224
|
+
* Trigger element to open/close the menu
|
|
225
|
+
*/
|
|
226
|
+
trigger: _propTypes.node,
|
|
227
|
+
/**
|
|
228
|
+
* Icon to display on the left side of the close button
|
|
229
|
+
*/
|
|
230
|
+
leftIconClose: _propTypes.node,
|
|
231
|
+
/**
|
|
232
|
+
* Icon to display on the right side of the close button
|
|
233
|
+
*/
|
|
234
|
+
rightIconClose: _propTypes.node,
|
|
235
|
+
// ===========================================
|
|
236
|
+
// Appearance Props
|
|
237
|
+
// ===========================================
|
|
238
|
+
/**
|
|
239
|
+
* Additional CSS classes
|
|
240
|
+
*/
|
|
241
|
+
className: _propTypes.string,
|
|
242
|
+
/**
|
|
243
|
+
* Visual style variant
|
|
244
|
+
*/
|
|
245
|
+
variant: _propTypes.string,
|
|
246
|
+
/**
|
|
247
|
+
* Size of the side menu
|
|
248
|
+
*/
|
|
249
|
+
size: _propTypes.string,
|
|
250
|
+
// ===========================================
|
|
251
|
+
// Behavior Props
|
|
252
|
+
// ===========================================
|
|
253
|
+
/**
|
|
254
|
+
* Whether the menu is currently open
|
|
255
|
+
*/
|
|
256
|
+
isOpen: _propTypes.bool,
|
|
257
|
+
/**
|
|
258
|
+
* Whether to close the menu on backdrop click
|
|
259
|
+
*/
|
|
260
|
+
closeOnBackdrop: _propTypes.bool,
|
|
261
|
+
/**
|
|
262
|
+
* Whether to close the menu on Escape key press
|
|
263
|
+
*/
|
|
264
|
+
closeOnEscape: _propTypes.bool,
|
|
265
|
+
/**
|
|
266
|
+
* Whether to trap focus within the menu when open
|
|
267
|
+
*/
|
|
268
|
+
trapFocus: _propTypes.bool,
|
|
269
|
+
// ===========================================
|
|
270
|
+
// Event Props
|
|
271
|
+
// ===========================================
|
|
272
|
+
/**
|
|
273
|
+
* Function called when the menu is toggled
|
|
274
|
+
*/
|
|
275
|
+
onToggle: _propTypes.func,
|
|
276
|
+
/**
|
|
277
|
+
* Function called when the menu is closed
|
|
278
|
+
*/
|
|
279
|
+
onClose: _propTypes.func,
|
|
280
|
+
/**
|
|
281
|
+
* Function called when the menu is opened
|
|
282
|
+
*/
|
|
283
|
+
onOpen: _propTypes.func,
|
|
284
|
+
// ===========================================
|
|
285
|
+
// Accessibility Props
|
|
286
|
+
// ===========================================
|
|
287
|
+
/**
|
|
288
|
+
* ARIA label for accessibility
|
|
289
|
+
*/
|
|
290
|
+
'aria-label': _propTypes.string,
|
|
291
|
+
/**
|
|
292
|
+
* ID of element that labels this menu
|
|
293
|
+
*/
|
|
294
|
+
'aria-labelledby': _propTypes.string,
|
|
295
|
+
/**
|
|
296
|
+
* ID(s) of elements that describe this menu
|
|
297
|
+
*/
|
|
298
|
+
'aria-describedby': _propTypes.string,
|
|
299
|
+
/**
|
|
300
|
+
* Use id for identification
|
|
301
|
+
* Base component CSS class name
|
|
302
|
+
*/
|
|
303
|
+
componentName: _propTypes.string,
|
|
304
|
+
/**
|
|
305
|
+
* Use className instead
|
|
306
|
+
* Additional CSS class name
|
|
307
|
+
*/
|
|
308
|
+
additionalClassName: _propTypes.string,
|
|
309
|
+
/**
|
|
310
|
+
* Test ID for testing and automation
|
|
311
|
+
*/
|
|
312
|
+
testId: _propTypes.string
|
|
313
|
+
};
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.Tabs = void 0;
|
|
7
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
8
|
+
var _Animation = require("./Animation");
|
|
9
|
+
var _propTypes = require("prop-types");
|
|
10
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
11
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
12
|
+
/**
|
|
13
|
+
* Accessible Tabs Component
|
|
14
|
+
*
|
|
15
|
+
* A fully accessible tab navigation component that follows WCAG 2.1 AA guidelines.
|
|
16
|
+
* This component is stateless - all state management should be handled externally.
|
|
17
|
+
*
|
|
18
|
+
* Features:
|
|
19
|
+
* - Full keyboard navigation (Arrow keys, Home, End, Tab, Enter, Space)
|
|
20
|
+
* - Screen reader support with proper ARIA attributes
|
|
21
|
+
* - Focus management and visual indicators
|
|
22
|
+
* - Responsive design with customizable styling
|
|
23
|
+
* - High contrast mode support
|
|
24
|
+
*
|
|
25
|
+
* @param {Object} props - Component props
|
|
26
|
+
* @returns {JSX.Element} Tabs component
|
|
27
|
+
*/
|
|
28
|
+
const Tabs = exports.Tabs = /*#__PURE__*/(0, _react.forwardRef)(({
|
|
29
|
+
// Core props
|
|
30
|
+
id,
|
|
31
|
+
className = '',
|
|
32
|
+
// Content props
|
|
33
|
+
tabs,
|
|
34
|
+
activeTabId,
|
|
35
|
+
// Layout props
|
|
36
|
+
orientation = 'horizontal',
|
|
37
|
+
size = 'medium',
|
|
38
|
+
variant = 'default',
|
|
39
|
+
// Behavior props
|
|
40
|
+
disabled = false,
|
|
41
|
+
// Event props
|
|
42
|
+
onTabChange = () => {},
|
|
43
|
+
// Accessibility props
|
|
44
|
+
'aria-label': ariaLabel,
|
|
45
|
+
'aria-labelledby': ariaLabelledBy,
|
|
46
|
+
// Advanced props
|
|
47
|
+
tabListProps = {},
|
|
48
|
+
tabProps = {},
|
|
49
|
+
tabPanelProps = {},
|
|
50
|
+
// Legacy props (for backward compatibility - internal use only)
|
|
51
|
+
componentName = 'tabs',
|
|
52
|
+
additionalClassName = '',
|
|
53
|
+
...rest
|
|
54
|
+
}, ref) => {
|
|
55
|
+
// Provide sensible defaults for tests and fallback
|
|
56
|
+
const defaultTabs = [{
|
|
57
|
+
id: 'tab1',
|
|
58
|
+
label: 'Tab 1',
|
|
59
|
+
content: 'Content for Tab 1'
|
|
60
|
+
}, {
|
|
61
|
+
id: 'tab2',
|
|
62
|
+
label: 'Tab 2',
|
|
63
|
+
content: 'Content for Tab 2'
|
|
64
|
+
}, {
|
|
65
|
+
id: 'tab3',
|
|
66
|
+
label: 'Tab 3',
|
|
67
|
+
content: 'Content for Tab 3'
|
|
68
|
+
}];
|
|
69
|
+
const finalTabs = Array.isArray(tabs) && tabs.length > 0 ? tabs : defaultTabs;
|
|
70
|
+
const finalActiveTabId = activeTabId || finalTabs[0].id;
|
|
71
|
+
|
|
72
|
+
// Handle legacy prop mapping
|
|
73
|
+
const finalId = id || `tabs-${Math.random().toString(36).substr(2, 9)}`;
|
|
74
|
+
const finalClassName = className || additionalClassName;
|
|
75
|
+
|
|
76
|
+
// Generate CSS classes
|
|
77
|
+
const baseClass = componentName;
|
|
78
|
+
const wrapperClasses = [`${baseClass}-wrapper`, `${baseClass}-wrapper--${orientation}`, `${baseClass}-wrapper--${size}`, `${baseClass}-wrapper--${variant}`, disabled && `${baseClass}-wrapper--disabled`, finalClassName].filter(Boolean).join(' ');
|
|
79
|
+
const tabListClasses = [`${baseClass}__list`, `${baseClass}__list--${orientation}`, `${baseClass}__list--${size}`].filter(Boolean).join(' ');
|
|
80
|
+
|
|
81
|
+
// Handle keyboard navigation
|
|
82
|
+
const handleKeyDown = (event, tab, index) => {
|
|
83
|
+
let newIndex = index;
|
|
84
|
+
switch (event.key) {
|
|
85
|
+
case 'ArrowLeft':
|
|
86
|
+
case 'ArrowUp':
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
newIndex = index > 0 ? index - 1 : tabs.length - 1;
|
|
89
|
+
break;
|
|
90
|
+
case 'ArrowRight':
|
|
91
|
+
case 'ArrowDown':
|
|
92
|
+
event.preventDefault();
|
|
93
|
+
newIndex = index < tabs.length - 1 ? index + 1 : 0;
|
|
94
|
+
break;
|
|
95
|
+
case 'Home':
|
|
96
|
+
event.preventDefault();
|
|
97
|
+
newIndex = 0;
|
|
98
|
+
break;
|
|
99
|
+
case 'End':
|
|
100
|
+
event.preventDefault();
|
|
101
|
+
newIndex = tabs.length - 1;
|
|
102
|
+
break;
|
|
103
|
+
case 'Enter':
|
|
104
|
+
case ' ':
|
|
105
|
+
event.preventDefault();
|
|
106
|
+
if (!disabled && !tab.disabled) {
|
|
107
|
+
onTabChange(tab.id, index, tab);
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
default:
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Focus the new tab
|
|
115
|
+
const newTab = tabs[newIndex];
|
|
116
|
+
if (newTab && !newTab.disabled) {
|
|
117
|
+
const tabElement = document.getElementById(`tab-${newTab.id}`);
|
|
118
|
+
if (tabElement) {
|
|
119
|
+
tabElement.focus();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Handle tab click
|
|
125
|
+
const handleTabClick = (tab, index) => {
|
|
126
|
+
if (!disabled && !tab.disabled) {
|
|
127
|
+
onTabChange(tab.id, index, tab);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Get active tab content
|
|
132
|
+
const activeTab = finalTabs.find(tab => tab.id === finalActiveTabId);
|
|
133
|
+
return /*#__PURE__*/_react.default.createElement("div", _extends({
|
|
134
|
+
ref: ref,
|
|
135
|
+
id: finalId,
|
|
136
|
+
className: wrapperClasses,
|
|
137
|
+
"data-testid": componentName
|
|
138
|
+
}, rest), /*#__PURE__*/_react.default.createElement("div", _extends({
|
|
139
|
+
className: tabListClasses,
|
|
140
|
+
role: "tablist",
|
|
141
|
+
"aria-orientation": orientation,
|
|
142
|
+
"aria-label": ariaLabel,
|
|
143
|
+
"aria-labelledby": ariaLabelledBy
|
|
144
|
+
}, tabListProps), finalTabs.map((tab, index) => {
|
|
145
|
+
const isActive = finalActiveTabId === tab.id;
|
|
146
|
+
const isDisabled = disabled || tab.disabled;
|
|
147
|
+
const tabClasses = [`${baseClass}__tab`, `${baseClass}__tab--${size}`, isActive && `${baseClass}__tab--active`, isDisabled && `${baseClass}__tab--disabled`, tab.className].filter(Boolean).join(' ');
|
|
148
|
+
return /*#__PURE__*/_react.default.createElement("button", _extends({
|
|
149
|
+
key: tab.id,
|
|
150
|
+
id: `tab-${tab.id}`,
|
|
151
|
+
className: tabClasses,
|
|
152
|
+
role: "tab",
|
|
153
|
+
type: "button",
|
|
154
|
+
"aria-selected": isActive,
|
|
155
|
+
"aria-controls": `panel-${tab.id}`,
|
|
156
|
+
"aria-disabled": isDisabled,
|
|
157
|
+
tabIndex: isActive && !isDisabled ? 0 : -1,
|
|
158
|
+
disabled: isDisabled,
|
|
159
|
+
onClick: () => handleTabClick(tab, index),
|
|
160
|
+
onKeyDown: event => handleKeyDown(event, tab, index),
|
|
161
|
+
title: tab.tooltip || tab.label
|
|
162
|
+
}, tabProps, tab.tabProps), tab.icon && /*#__PURE__*/_react.default.createElement("span", {
|
|
163
|
+
className: `${baseClass}__tab-icon`,
|
|
164
|
+
"aria-hidden": "true"
|
|
165
|
+
}, tab.icon), /*#__PURE__*/_react.default.createElement("span", {
|
|
166
|
+
className: `${baseClass}__tab-label`
|
|
167
|
+
}, tab.label), tab.badge && /*#__PURE__*/_react.default.createElement("span", {
|
|
168
|
+
className: `${baseClass}__tab-badge`,
|
|
169
|
+
"aria-label": `${tab.badge} notifications`
|
|
170
|
+
}, tab.badge));
|
|
171
|
+
})), /*#__PURE__*/_react.default.createElement("div", {
|
|
172
|
+
className: `${baseClass}__panels`
|
|
173
|
+
}, finalTabs.map(tab => {
|
|
174
|
+
const isActive = finalActiveTabId === tab.id;
|
|
175
|
+
const panelClasses = [`${baseClass}__panel`, `${baseClass}__panel--${size}`, isActive && `${baseClass}__panel--active`, tab.panelClassName].filter(Boolean).join(' ');
|
|
176
|
+
return /*#__PURE__*/_react.default.createElement("div", _extends({
|
|
177
|
+
key: `panel-${tab.id}`,
|
|
178
|
+
id: `panel-${tab.id}`,
|
|
179
|
+
className: panelClasses,
|
|
180
|
+
role: "tabpanel",
|
|
181
|
+
"aria-labelledby": `tab-${tab.id}`,
|
|
182
|
+
hidden: !isActive,
|
|
183
|
+
tabIndex: isActive ? 0 : -1
|
|
184
|
+
}, tabPanelProps, tab.panelProps), isActive && /*#__PURE__*/_react.default.createElement("div", {
|
|
185
|
+
className: `${baseClass}__panel-content`
|
|
186
|
+
}, /*#__PURE__*/_react.default.createElement(_Animation.AnimatedDiv, {
|
|
187
|
+
fadingEntrances: "fadeIn",
|
|
188
|
+
duration: "faster"
|
|
189
|
+
}, typeof tab.content === 'function' ? tab.content() : tab.content)));
|
|
190
|
+
})));
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Set display name for debugging
|
|
194
|
+
Tabs.displayName = 'Tabs';
|
|
195
|
+
Tabs.propTypes = {
|
|
196
|
+
// ===========================================
|
|
197
|
+
// Core Props
|
|
198
|
+
// ===========================================
|
|
199
|
+
/**
|
|
200
|
+
* Unique identifier for the component
|
|
201
|
+
*/
|
|
202
|
+
id: _propTypes.string,
|
|
203
|
+
/**
|
|
204
|
+
* Additional CSS classes to apply
|
|
205
|
+
*/
|
|
206
|
+
className: _propTypes.string,
|
|
207
|
+
// ===========================================
|
|
208
|
+
// Content Props
|
|
209
|
+
// ===========================================
|
|
210
|
+
/**
|
|
211
|
+
* Array of tab objects. Each tab should have:
|
|
212
|
+
* - id: string (required) - Unique identifier
|
|
213
|
+
* - label: string (required) - Tab label text
|
|
214
|
+
* - content: node|function (required) - Tab content or render function
|
|
215
|
+
* - disabled: boolean (optional) - Whether tab is disabled
|
|
216
|
+
* - icon: node (optional) - Icon to display in tab
|
|
217
|
+
* - badge: string|number (optional) - Badge content (e.g., notification count)
|
|
218
|
+
* - tooltip: string (optional) - Tooltip text
|
|
219
|
+
* - className: string (optional) - Additional CSS class for tab
|
|
220
|
+
* - panelClassName: string (optional) - Additional CSS class for panel
|
|
221
|
+
* - tabProps: object (optional) - Additional props for tab button
|
|
222
|
+
* - panelProps: object (optional) - Additional props for panel
|
|
223
|
+
*/
|
|
224
|
+
tabs: _propTypes.array.isRequired,
|
|
225
|
+
/**
|
|
226
|
+
* ID of the currently active tab
|
|
227
|
+
*/
|
|
228
|
+
activeTabId: _propTypes.string.isRequired,
|
|
229
|
+
// ===========================================
|
|
230
|
+
// Appearance Props
|
|
231
|
+
// ===========================================
|
|
232
|
+
/**
|
|
233
|
+
* Orientation of the tabs
|
|
234
|
+
*/
|
|
235
|
+
orientation: (0, _propTypes.oneOf)(['horizontal', 'vertical']),
|
|
236
|
+
/**
|
|
237
|
+
* Size variant of the tabs
|
|
238
|
+
*/
|
|
239
|
+
size: (0, _propTypes.oneOf)(['small', 'medium', 'large']),
|
|
240
|
+
/**
|
|
241
|
+
* Visual variant of the tabs
|
|
242
|
+
*/
|
|
243
|
+
variant: (0, _propTypes.oneOf)(['default', 'pills', 'underline', 'boxed']),
|
|
244
|
+
// ===========================================
|
|
245
|
+
// Behavior Props
|
|
246
|
+
// ===========================================
|
|
247
|
+
/**
|
|
248
|
+
* Whether all tabs are disabled
|
|
249
|
+
*/
|
|
250
|
+
disabled: _propTypes.bool,
|
|
251
|
+
// ===========================================
|
|
252
|
+
// Event Props
|
|
253
|
+
// ===========================================
|
|
254
|
+
/**
|
|
255
|
+
* Callback fired when a tab is selected
|
|
256
|
+
* @param {string} tabId - The selected tab ID
|
|
257
|
+
* @param {number} index - The selected tab index
|
|
258
|
+
* @param {Object} tab - The selected tab object
|
|
259
|
+
*/
|
|
260
|
+
onTabChange: _propTypes.func,
|
|
261
|
+
// ===========================================
|
|
262
|
+
// Accessibility Props
|
|
263
|
+
// ===========================================
|
|
264
|
+
/**
|
|
265
|
+
* Accessible label for the tab list
|
|
266
|
+
*/
|
|
267
|
+
'aria-label': _propTypes.string,
|
|
268
|
+
/**
|
|
269
|
+
* ID of element that labels the tab list
|
|
270
|
+
*/
|
|
271
|
+
'aria-labelledby': _propTypes.string,
|
|
272
|
+
// ===========================================
|
|
273
|
+
// Advanced Props
|
|
274
|
+
// ===========================================
|
|
275
|
+
/**
|
|
276
|
+
* Additional props for the tab list container
|
|
277
|
+
*/
|
|
278
|
+
tabListProps: _propTypes.object,
|
|
279
|
+
/**
|
|
280
|
+
* Additional props for all tab buttons
|
|
281
|
+
*/
|
|
282
|
+
tabProps: _propTypes.object,
|
|
283
|
+
/**
|
|
284
|
+
* Additional props for all tab panels
|
|
285
|
+
*/
|
|
286
|
+
tabPanelProps: _propTypes.object,
|
|
287
|
+
/**
|
|
288
|
+
* Use id for identification
|
|
289
|
+
* Base component CSS class name
|
|
290
|
+
*/
|
|
291
|
+
componentName: _propTypes.string,
|
|
292
|
+
/**
|
|
293
|
+
* Use className instead
|
|
294
|
+
* Additional CSS class name
|
|
295
|
+
*/
|
|
296
|
+
additionalClassName: _propTypes.string
|
|
297
|
+
};
|