@abstraks-dev/ui-library 2.2.1 → 2.4.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/README.md +99 -0
- package/dist/__tests__/Footer.test.js +3 -3
- package/dist/__tests__/Modal.test.js +480 -0
- package/dist/__tests__/Prompt.test.js +211 -0
- package/dist/components/Modal.js +195 -0
- package/dist/index.js +16 -0
- package/dist/styles/main.css +218 -0
- package/dist/styles/main.css.map +1 -1
- package/dist/styles/modal.css +332 -0
- package/dist/styles/modal.css.map +1 -0
- package/dist/styles/modal.scss +253 -0
- package/package.json +2 -2
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _react = _interopRequireDefault(require("react"));
|
|
4
|
+
var _react2 = require("@testing-library/react");
|
|
5
|
+
var _Prompt = require("../components/Prompt");
|
|
6
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
|
+
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); }
|
|
8
|
+
describe('Prompt Component', () => {
|
|
9
|
+
const defaultProps = {
|
|
10
|
+
message: 'Are you sure you want to proceed?',
|
|
11
|
+
onConfirm: jest.fn(),
|
|
12
|
+
onCancel: jest.fn(),
|
|
13
|
+
open: true
|
|
14
|
+
};
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
jest.clearAllMocks();
|
|
17
|
+
});
|
|
18
|
+
describe('Rendering', () => {
|
|
19
|
+
test('renders prompt when open is true', () => {
|
|
20
|
+
(0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, defaultProps));
|
|
21
|
+
expect(_react2.screen.getByRole('dialog')).toBeInTheDocument();
|
|
22
|
+
expect(_react2.screen.getByText('Are you sure you want to proceed?')).toBeInTheDocument();
|
|
23
|
+
});
|
|
24
|
+
test('does not render when open is false', () => {
|
|
25
|
+
(0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, _extends({}, defaultProps, {
|
|
26
|
+
open: false
|
|
27
|
+
})));
|
|
28
|
+
expect(_react2.screen.queryByRole('dialog')).not.toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
test('renders with default confirm button text', () => {
|
|
31
|
+
(0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, defaultProps));
|
|
32
|
+
expect(_react2.screen.getByText('Yes')).toBeInTheDocument();
|
|
33
|
+
});
|
|
34
|
+
test('renders with custom confirm button text', () => {
|
|
35
|
+
(0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, _extends({}, defaultProps, {
|
|
36
|
+
confirmText: "Delete"
|
|
37
|
+
})));
|
|
38
|
+
expect(_react2.screen.getByText('Delete')).toBeInTheDocument();
|
|
39
|
+
expect(_react2.screen.queryByText('Yes')).not.toBeInTheDocument();
|
|
40
|
+
});
|
|
41
|
+
test('renders with default cancel button text', () => {
|
|
42
|
+
(0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, defaultProps));
|
|
43
|
+
expect(_react2.screen.getByText('Cancel')).toBeInTheDocument();
|
|
44
|
+
});
|
|
45
|
+
test('renders with custom cancel button text', () => {
|
|
46
|
+
(0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, _extends({}, defaultProps, {
|
|
47
|
+
cancelText: "No"
|
|
48
|
+
})));
|
|
49
|
+
expect(_react2.screen.getByText('No')).toBeInTheDocument();
|
|
50
|
+
expect(_react2.screen.queryByText('Cancel')).not.toBeInTheDocument();
|
|
51
|
+
});
|
|
52
|
+
test('renders both confirm and cancel buttons by default', () => {
|
|
53
|
+
(0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, defaultProps));
|
|
54
|
+
expect(_react2.screen.getByText('Yes')).toBeInTheDocument();
|
|
55
|
+
expect(_react2.screen.getByText('Cancel')).toBeInTheDocument();
|
|
56
|
+
});
|
|
57
|
+
test('hides cancel button when showCancel is false', () => {
|
|
58
|
+
(0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, _extends({}, defaultProps, {
|
|
59
|
+
showCancel: false
|
|
60
|
+
})));
|
|
61
|
+
expect(_react2.screen.getByText('Yes')).toBeInTheDocument();
|
|
62
|
+
expect(_react2.screen.queryByText('Cancel')).not.toBeInTheDocument();
|
|
63
|
+
});
|
|
64
|
+
test('renders without confirm button if onConfirm is not provided', () => {
|
|
65
|
+
(0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, {
|
|
66
|
+
message: "Information message",
|
|
67
|
+
onCancel: defaultProps.onCancel,
|
|
68
|
+
open: true
|
|
69
|
+
}));
|
|
70
|
+
expect(_react2.screen.queryByText('Yes')).not.toBeInTheDocument();
|
|
71
|
+
expect(_react2.screen.getByText('Cancel')).toBeInTheDocument();
|
|
72
|
+
});
|
|
73
|
+
test('has correct ARIA attributes', () => {
|
|
74
|
+
(0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, defaultProps));
|
|
75
|
+
const dialog = _react2.screen.getByRole('dialog');
|
|
76
|
+
expect(dialog).toHaveAttribute('aria-modal', 'true');
|
|
77
|
+
expect(dialog).toHaveAttribute('aria-label', 'Are you sure you want to proceed?');
|
|
78
|
+
});
|
|
79
|
+
test('confirm button receives focus when rendered', () => {
|
|
80
|
+
(0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, defaultProps));
|
|
81
|
+
const confirmButton = _react2.screen.getByText('Yes');
|
|
82
|
+
expect(confirmButton).toHaveFocus();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
describe('User Interactions', () => {
|
|
86
|
+
test('calls onConfirm when confirm button is clicked', () => {
|
|
87
|
+
(0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, defaultProps));
|
|
88
|
+
const confirmButton = _react2.screen.getByText('Yes');
|
|
89
|
+
_react2.fireEvent.click(confirmButton);
|
|
90
|
+
expect(defaultProps.onConfirm).toHaveBeenCalledTimes(1);
|
|
91
|
+
expect(defaultProps.onCancel).not.toHaveBeenCalled();
|
|
92
|
+
});
|
|
93
|
+
test('calls onCancel when cancel button is clicked', () => {
|
|
94
|
+
(0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, defaultProps));
|
|
95
|
+
const cancelButton = _react2.screen.getByText('Cancel');
|
|
96
|
+
_react2.fireEvent.click(cancelButton);
|
|
97
|
+
expect(defaultProps.onCancel).toHaveBeenCalledTimes(1);
|
|
98
|
+
expect(defaultProps.onConfirm).not.toHaveBeenCalled();
|
|
99
|
+
});
|
|
100
|
+
test('does not call onConfirm if not provided', () => {
|
|
101
|
+
const mockOnCancel = jest.fn();
|
|
102
|
+
(0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, {
|
|
103
|
+
message: "Test message",
|
|
104
|
+
onCancel: mockOnCancel,
|
|
105
|
+
open: true,
|
|
106
|
+
showCancel: true
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
// Only cancel button should be present
|
|
110
|
+
expect(_react2.screen.queryByText('Yes')).not.toBeInTheDocument();
|
|
111
|
+
});
|
|
112
|
+
test('handles multiple clicks correctly', () => {
|
|
113
|
+
(0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, defaultProps));
|
|
114
|
+
const confirmButton = _react2.screen.getByText('Yes');
|
|
115
|
+
_react2.fireEvent.click(confirmButton);
|
|
116
|
+
_react2.fireEvent.click(confirmButton);
|
|
117
|
+
expect(defaultProps.onConfirm).toHaveBeenCalledTimes(2);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
describe('PropTypes Validation', () => {
|
|
121
|
+
test('renders with required props only', () => {
|
|
122
|
+
(0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, {
|
|
123
|
+
message: "Required message",
|
|
124
|
+
open: true
|
|
125
|
+
}));
|
|
126
|
+
expect(_react2.screen.getByRole('dialog')).toBeInTheDocument();
|
|
127
|
+
expect(_react2.screen.getByText('Required message')).toBeInTheDocument();
|
|
128
|
+
});
|
|
129
|
+
test('renders with all optional props', () => {
|
|
130
|
+
(0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, {
|
|
131
|
+
message: "All props message",
|
|
132
|
+
onConfirm: jest.fn(),
|
|
133
|
+
onCancel: jest.fn(),
|
|
134
|
+
confirmText: "Confirm",
|
|
135
|
+
cancelText: "Dismiss",
|
|
136
|
+
showCancel: true,
|
|
137
|
+
open: true
|
|
138
|
+
}));
|
|
139
|
+
expect(_react2.screen.getByText('All props message')).toBeInTheDocument();
|
|
140
|
+
expect(_react2.screen.getByText('Confirm')).toBeInTheDocument();
|
|
141
|
+
expect(_react2.screen.getByText('Dismiss')).toBeInTheDocument();
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
describe('CSS Classes', () => {
|
|
145
|
+
test('applies correct CSS classes', () => {
|
|
146
|
+
const {
|
|
147
|
+
container
|
|
148
|
+
} = (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, defaultProps));
|
|
149
|
+
expect(container.querySelector('.prompt-overlay')).toBeInTheDocument();
|
|
150
|
+
expect(container.querySelector('.prompt-box')).toBeInTheDocument();
|
|
151
|
+
expect(container.querySelector('.prompt-actions')).toBeInTheDocument();
|
|
152
|
+
});
|
|
153
|
+
test('confirm button has correct class', () => {
|
|
154
|
+
(0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, defaultProps));
|
|
155
|
+
const confirmButton = _react2.screen.getByText('Yes');
|
|
156
|
+
expect(confirmButton).toHaveClass('prompt-confirm');
|
|
157
|
+
});
|
|
158
|
+
test('cancel button has correct class', () => {
|
|
159
|
+
(0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, defaultProps));
|
|
160
|
+
const cancelButton = _react2.screen.getByText('Cancel');
|
|
161
|
+
expect(cancelButton).toHaveClass('prompt-cancel');
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
describe('Edge Cases', () => {
|
|
165
|
+
test('renders with empty message string', () => {
|
|
166
|
+
(0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, _extends({}, defaultProps, {
|
|
167
|
+
message: ""
|
|
168
|
+
})));
|
|
169
|
+
expect(_react2.screen.getByRole('dialog')).toBeInTheDocument();
|
|
170
|
+
});
|
|
171
|
+
test('renders with very long message', () => {
|
|
172
|
+
const longMessage = 'This is a very long message that might wrap to multiple lines in the prompt dialog and should still render correctly without breaking the layout or causing any issues';
|
|
173
|
+
(0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, _extends({}, defaultProps, {
|
|
174
|
+
message: longMessage
|
|
175
|
+
})));
|
|
176
|
+
expect(_react2.screen.getByText(longMessage)).toBeInTheDocument();
|
|
177
|
+
});
|
|
178
|
+
test('renders when both onConfirm and onCancel are undefined', () => {
|
|
179
|
+
(0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, {
|
|
180
|
+
message: "Message",
|
|
181
|
+
open: true
|
|
182
|
+
}));
|
|
183
|
+
expect(_react2.screen.getByRole('dialog')).toBeInTheDocument();
|
|
184
|
+
expect(_react2.screen.getByText('Message')).toBeInTheDocument();
|
|
185
|
+
});
|
|
186
|
+
test('transitions from closed to open', () => {
|
|
187
|
+
const {
|
|
188
|
+
rerender
|
|
189
|
+
} = (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, _extends({}, defaultProps, {
|
|
190
|
+
open: false
|
|
191
|
+
})));
|
|
192
|
+
expect(_react2.screen.queryByRole('dialog')).not.toBeInTheDocument();
|
|
193
|
+
rerender(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, _extends({}, defaultProps, {
|
|
194
|
+
open: true
|
|
195
|
+
})));
|
|
196
|
+
expect(_react2.screen.getByRole('dialog')).toBeInTheDocument();
|
|
197
|
+
});
|
|
198
|
+
test('transitions from open to closed', () => {
|
|
199
|
+
const {
|
|
200
|
+
rerender
|
|
201
|
+
} = (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, _extends({}, defaultProps, {
|
|
202
|
+
open: true
|
|
203
|
+
})));
|
|
204
|
+
expect(_react2.screen.getByRole('dialog')).toBeInTheDocument();
|
|
205
|
+
rerender(/*#__PURE__*/_react.default.createElement(_Prompt.Prompt, _extends({}, defaultProps, {
|
|
206
|
+
open: false
|
|
207
|
+
})));
|
|
208
|
+
expect(_react2.screen.queryByRole('dialog')).not.toBeInTheDocument();
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
});
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = exports.Modal = void 0;
|
|
7
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
8
|
+
var _reactDom = require("react-dom");
|
|
9
|
+
var _propTypes = require("prop-types");
|
|
10
|
+
var _icons = require("../icons");
|
|
11
|
+
var _ssrSafeId = require("../utils/ssrSafeId");
|
|
12
|
+
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); }
|
|
13
|
+
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); }
|
|
14
|
+
/**
|
|
15
|
+
* Modal - A stateless, accessible modal dialog component
|
|
16
|
+
*
|
|
17
|
+
* Features:
|
|
18
|
+
* - Portal rendering to document.body
|
|
19
|
+
* - Multiple sizes (sm, md, lg, xl, full)
|
|
20
|
+
* - Accessible with proper ARIA attributes and focus management
|
|
21
|
+
* - Keyboard support (Escape to close)
|
|
22
|
+
* - Backdrop click to close (optional)
|
|
23
|
+
* - Scroll lock on body
|
|
24
|
+
* - Header, body, and footer sections
|
|
25
|
+
* - Customizable close button
|
|
26
|
+
* - Animation support
|
|
27
|
+
*
|
|
28
|
+
* @component
|
|
29
|
+
* @example
|
|
30
|
+
* <Modal
|
|
31
|
+
* isOpen={true}
|
|
32
|
+
* onClose={() => setIsOpen(false)}
|
|
33
|
+
* title="Confirm Action"
|
|
34
|
+
* size="md"
|
|
35
|
+
* >
|
|
36
|
+
* <p>Are you sure you want to continue?</p>
|
|
37
|
+
* </Modal>
|
|
38
|
+
*/
|
|
39
|
+
const Modal = exports.Modal = /*#__PURE__*/(0, _react.forwardRef)(({
|
|
40
|
+
// Core props
|
|
41
|
+
id,
|
|
42
|
+
className = '',
|
|
43
|
+
// Content props
|
|
44
|
+
title,
|
|
45
|
+
children,
|
|
46
|
+
footer,
|
|
47
|
+
headerContent,
|
|
48
|
+
// Behavior props
|
|
49
|
+
isOpen = false,
|
|
50
|
+
onClose,
|
|
51
|
+
closeOnBackdropClick = true,
|
|
52
|
+
closeOnEscape = true,
|
|
53
|
+
showCloseButton = true,
|
|
54
|
+
preventBodyScroll = true,
|
|
55
|
+
// Appearance props
|
|
56
|
+
size = 'md',
|
|
57
|
+
centered = true,
|
|
58
|
+
// Accessibility props
|
|
59
|
+
'aria-label': ariaLabel,
|
|
60
|
+
'aria-describedby': ariaDescribedBy,
|
|
61
|
+
'aria-labelledby': ariaLabelledBy,
|
|
62
|
+
role = 'dialog',
|
|
63
|
+
// Additional props
|
|
64
|
+
componentName = 'modal',
|
|
65
|
+
...rest
|
|
66
|
+
}, ref) => {
|
|
67
|
+
const generateId = (0, _ssrSafeId.useStableId)('modal');
|
|
68
|
+
const stableId = id || generateId();
|
|
69
|
+
const titleId = `${stableId}-title`;
|
|
70
|
+
const contentId = `${stableId}-content`;
|
|
71
|
+
|
|
72
|
+
// Handle Escape key press
|
|
73
|
+
const handleEscape = (0, _react.useCallback)(event => {
|
|
74
|
+
if (closeOnEscape && event.key === 'Escape' && isOpen) {
|
|
75
|
+
event.preventDefault();
|
|
76
|
+
onClose?.();
|
|
77
|
+
}
|
|
78
|
+
}, [closeOnEscape, isOpen, onClose]);
|
|
79
|
+
|
|
80
|
+
// Handle backdrop click
|
|
81
|
+
const handleBackdropClick = (0, _react.useCallback)(event => {
|
|
82
|
+
if (closeOnBackdropClick && event.target === event.currentTarget) {
|
|
83
|
+
onClose?.();
|
|
84
|
+
}
|
|
85
|
+
}, [closeOnBackdropClick, onClose]);
|
|
86
|
+
|
|
87
|
+
// Manage body scroll lock
|
|
88
|
+
(0, _react.useEffect)(() => {
|
|
89
|
+
if (isOpen && preventBodyScroll) {
|
|
90
|
+
const originalOverflow = document.body.style.overflow;
|
|
91
|
+
const originalPaddingRight = document.body.style.paddingRight;
|
|
92
|
+
|
|
93
|
+
// Get scrollbar width
|
|
94
|
+
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
|
|
95
|
+
|
|
96
|
+
// Lock scroll
|
|
97
|
+
document.body.style.overflow = 'hidden';
|
|
98
|
+
if (scrollbarWidth > 0) {
|
|
99
|
+
document.body.style.paddingRight = `${scrollbarWidth}px`;
|
|
100
|
+
}
|
|
101
|
+
return () => {
|
|
102
|
+
document.body.style.overflow = originalOverflow;
|
|
103
|
+
document.body.style.paddingRight = originalPaddingRight;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}, [isOpen, preventBodyScroll]);
|
|
107
|
+
|
|
108
|
+
// Add/remove escape key listener
|
|
109
|
+
(0, _react.useEffect)(() => {
|
|
110
|
+
if (isOpen && closeOnEscape) {
|
|
111
|
+
document.addEventListener('keydown', handleEscape);
|
|
112
|
+
return () => {
|
|
113
|
+
document.removeEventListener('keydown', handleEscape);
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}, [isOpen, closeOnEscape, handleEscape]);
|
|
117
|
+
|
|
118
|
+
// Focus management
|
|
119
|
+
(0, _react.useEffect)(() => {
|
|
120
|
+
if (isOpen) {
|
|
121
|
+
// Store the currently focused element
|
|
122
|
+
const previousActiveElement = document.activeElement;
|
|
123
|
+
|
|
124
|
+
// Return focus when modal closes
|
|
125
|
+
return () => {
|
|
126
|
+
if (previousActiveElement && previousActiveElement.focus) {
|
|
127
|
+
previousActiveElement.focus();
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}, [isOpen]);
|
|
132
|
+
if (!isOpen) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const modalClassName = [componentName, `${componentName}--${size}`, centered && `${componentName}--centered`, className].filter(Boolean).join(' ');
|
|
136
|
+
const modalContent = /*#__PURE__*/_react.default.createElement("div", {
|
|
137
|
+
className: `${componentName}-overlay`,
|
|
138
|
+
onClick: handleBackdropClick
|
|
139
|
+
}, /*#__PURE__*/_react.default.createElement("div", _extends({
|
|
140
|
+
ref: ref,
|
|
141
|
+
id: stableId,
|
|
142
|
+
className: modalClassName,
|
|
143
|
+
role: role,
|
|
144
|
+
"aria-modal": "true",
|
|
145
|
+
"aria-label": ariaLabel || title,
|
|
146
|
+
"aria-labelledby": title ? titleId : ariaLabelledBy,
|
|
147
|
+
"aria-describedby": ariaDescribedBy || contentId
|
|
148
|
+
}, rest), (title || headerContent || showCloseButton) && /*#__PURE__*/_react.default.createElement("div", {
|
|
149
|
+
className: `${componentName}__header`
|
|
150
|
+
}, headerContent || /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, title && /*#__PURE__*/_react.default.createElement("h2", {
|
|
151
|
+
id: titleId,
|
|
152
|
+
className: `${componentName}__title`
|
|
153
|
+
}, title)), showCloseButton && /*#__PURE__*/_react.default.createElement("button", {
|
|
154
|
+
className: `${componentName}__close`,
|
|
155
|
+
onClick: onClose,
|
|
156
|
+
"aria-label": "Close modal",
|
|
157
|
+
type: "button"
|
|
158
|
+
}, /*#__PURE__*/_react.default.createElement(_icons.Close, {
|
|
159
|
+
dimensions: 24
|
|
160
|
+
}))), /*#__PURE__*/_react.default.createElement("div", {
|
|
161
|
+
id: contentId,
|
|
162
|
+
className: `${componentName}__body`
|
|
163
|
+
}, children), footer && /*#__PURE__*/_react.default.createElement("div", {
|
|
164
|
+
className: `${componentName}__footer`
|
|
165
|
+
}, footer)));
|
|
166
|
+
|
|
167
|
+
// Render modal in a portal
|
|
168
|
+
if (typeof window !== 'undefined') {
|
|
169
|
+
return /*#__PURE__*/(0, _reactDom.createPortal)(modalContent, document.body);
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
});
|
|
173
|
+
Modal.propTypes = {
|
|
174
|
+
id: _propTypes.string,
|
|
175
|
+
className: _propTypes.string,
|
|
176
|
+
title: _propTypes.string,
|
|
177
|
+
children: _propTypes.node,
|
|
178
|
+
footer: _propTypes.node,
|
|
179
|
+
headerContent: _propTypes.node,
|
|
180
|
+
isOpen: _propTypes.bool,
|
|
181
|
+
onClose: _propTypes.func,
|
|
182
|
+
closeOnBackdropClick: _propTypes.bool,
|
|
183
|
+
closeOnEscape: _propTypes.bool,
|
|
184
|
+
showCloseButton: _propTypes.bool,
|
|
185
|
+
preventBodyScroll: _propTypes.bool,
|
|
186
|
+
size: (0, _propTypes.oneOf)(['sm', 'md', 'lg', 'xl', 'full']),
|
|
187
|
+
centered: _propTypes.bool,
|
|
188
|
+
'aria-label': _propTypes.string,
|
|
189
|
+
'aria-describedby': _propTypes.string,
|
|
190
|
+
'aria-labelledby': _propTypes.string,
|
|
191
|
+
role: _propTypes.string,
|
|
192
|
+
componentName: _propTypes.string
|
|
193
|
+
};
|
|
194
|
+
Modal.displayName = 'Modal';
|
|
195
|
+
var _default = exports.default = Modal;
|
package/dist/index.js
CHANGED
|
@@ -24,7 +24,9 @@ var _exportNames = {
|
|
|
24
24
|
Hero: true,
|
|
25
25
|
Label: true,
|
|
26
26
|
Loader: true,
|
|
27
|
+
Modal: true,
|
|
27
28
|
Paragraph: true,
|
|
29
|
+
Prompt: true,
|
|
28
30
|
Radio: true,
|
|
29
31
|
Search: true,
|
|
30
32
|
Select: true,
|
|
@@ -355,6 +357,12 @@ Object.defineProperty(exports, "Magnify", {
|
|
|
355
357
|
return _Magnify.Magnify;
|
|
356
358
|
}
|
|
357
359
|
});
|
|
360
|
+
Object.defineProperty(exports, "Modal", {
|
|
361
|
+
enumerable: true,
|
|
362
|
+
get: function () {
|
|
363
|
+
return _Modal.default;
|
|
364
|
+
}
|
|
365
|
+
});
|
|
358
366
|
Object.defineProperty(exports, "News", {
|
|
359
367
|
enumerable: true,
|
|
360
368
|
get: function () {
|
|
@@ -373,6 +381,12 @@ Object.defineProperty(exports, "PlusCircle", {
|
|
|
373
381
|
return _PlusCircle.PlusCircle;
|
|
374
382
|
}
|
|
375
383
|
});
|
|
384
|
+
Object.defineProperty(exports, "Prompt", {
|
|
385
|
+
enumerable: true,
|
|
386
|
+
get: function () {
|
|
387
|
+
return _Prompt.default;
|
|
388
|
+
}
|
|
389
|
+
});
|
|
376
390
|
Object.defineProperty(exports, "Radio", {
|
|
377
391
|
enumerable: true,
|
|
378
392
|
get: function () {
|
|
@@ -483,7 +497,9 @@ var _Heading = _interopRequireDefault(require("./components/Heading"));
|
|
|
483
497
|
var _Hero = _interopRequireDefault(require("./components/Hero"));
|
|
484
498
|
var _Label = _interopRequireDefault(require("./components/Label"));
|
|
485
499
|
var _Loader = _interopRequireDefault(require("./components/Loader"));
|
|
500
|
+
var _Modal = _interopRequireDefault(require("./components/Modal"));
|
|
486
501
|
var _Paragraph = _interopRequireDefault(require("./components/Paragraph"));
|
|
502
|
+
var _Prompt = _interopRequireDefault(require("./components/Prompt"));
|
|
487
503
|
var _Radio = require("./components/Radio");
|
|
488
504
|
var _Search = require("./components/Search");
|
|
489
505
|
var _Select = require("./components/Select");
|
package/dist/styles/main.css
CHANGED
|
@@ -7545,6 +7545,224 @@ body.scroll-locked {
|
|
|
7545
7545
|
border: 0 !important;
|
|
7546
7546
|
}
|
|
7547
7547
|
|
|
7548
|
+
.modal-overlay {
|
|
7549
|
+
position: fixed;
|
|
7550
|
+
top: 0;
|
|
7551
|
+
left: 0;
|
|
7552
|
+
right: 0;
|
|
7553
|
+
bottom: 0;
|
|
7554
|
+
background-color: rgba(0, 0, 0, 0.6);
|
|
7555
|
+
backdrop-filter: blur(4px);
|
|
7556
|
+
z-index: 9999;
|
|
7557
|
+
display: flex;
|
|
7558
|
+
align-items: center;
|
|
7559
|
+
justify-content: center;
|
|
7560
|
+
padding: 1rem;
|
|
7561
|
+
animation: fadeIn 0.2s ease-out;
|
|
7562
|
+
overflow-y: auto;
|
|
7563
|
+
}
|
|
7564
|
+
@media (prefers-reduced-motion: reduce) {
|
|
7565
|
+
.modal-overlay {
|
|
7566
|
+
animation: none;
|
|
7567
|
+
}
|
|
7568
|
+
}
|
|
7569
|
+
|
|
7570
|
+
.modal {
|
|
7571
|
+
background: #fff;
|
|
7572
|
+
border-radius: 8px;
|
|
7573
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
7574
|
+
display: flex;
|
|
7575
|
+
flex-direction: column;
|
|
7576
|
+
max-height: calc(100vh - 2rem);
|
|
7577
|
+
width: 100%;
|
|
7578
|
+
animation: slideIn 0.3s ease-out;
|
|
7579
|
+
position: relative;
|
|
7580
|
+
}
|
|
7581
|
+
@media (prefers-reduced-motion: reduce) {
|
|
7582
|
+
.modal {
|
|
7583
|
+
animation: none;
|
|
7584
|
+
}
|
|
7585
|
+
}
|
|
7586
|
+
@media (prefers-color-scheme: dark) {
|
|
7587
|
+
.modal {
|
|
7588
|
+
background: #1e1e1e;
|
|
7589
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
|
|
7590
|
+
}
|
|
7591
|
+
}
|
|
7592
|
+
.modal--sm {
|
|
7593
|
+
max-width: 400px;
|
|
7594
|
+
}
|
|
7595
|
+
.modal--md {
|
|
7596
|
+
max-width: 600px;
|
|
7597
|
+
}
|
|
7598
|
+
.modal--lg {
|
|
7599
|
+
max-width: 800px;
|
|
7600
|
+
}
|
|
7601
|
+
.modal--xl {
|
|
7602
|
+
max-width: 1200px;
|
|
7603
|
+
}
|
|
7604
|
+
.modal--full {
|
|
7605
|
+
max-width: calc(100vw - 2rem);
|
|
7606
|
+
max-height: calc(100vh - 2rem);
|
|
7607
|
+
}
|
|
7608
|
+
.modal--centered {
|
|
7609
|
+
margin: auto;
|
|
7610
|
+
}
|
|
7611
|
+
.modal__header {
|
|
7612
|
+
display: flex;
|
|
7613
|
+
align-items: center;
|
|
7614
|
+
justify-content: space-between;
|
|
7615
|
+
padding: 1.5rem;
|
|
7616
|
+
border-bottom: 1px solid #e9ecef;
|
|
7617
|
+
flex-shrink: 0;
|
|
7618
|
+
}
|
|
7619
|
+
@media (prefers-color-scheme: dark) {
|
|
7620
|
+
.modal__header {
|
|
7621
|
+
border-bottom-color: #333;
|
|
7622
|
+
}
|
|
7623
|
+
}
|
|
7624
|
+
.modal__title {
|
|
7625
|
+
margin: 0;
|
|
7626
|
+
font-size: 1.25rem;
|
|
7627
|
+
font-weight: 600;
|
|
7628
|
+
color: #212529;
|
|
7629
|
+
flex: 1;
|
|
7630
|
+
}
|
|
7631
|
+
@media (prefers-color-scheme: dark) {
|
|
7632
|
+
.modal__title {
|
|
7633
|
+
color: #fff;
|
|
7634
|
+
}
|
|
7635
|
+
}
|
|
7636
|
+
.modal__close {
|
|
7637
|
+
display: flex;
|
|
7638
|
+
align-items: center;
|
|
7639
|
+
justify-content: center;
|
|
7640
|
+
background: transparent;
|
|
7641
|
+
border: none;
|
|
7642
|
+
padding: 0.5rem;
|
|
7643
|
+
margin: -0.5rem -0.5rem -0.5rem 1rem;
|
|
7644
|
+
cursor: pointer;
|
|
7645
|
+
color: #6c757d;
|
|
7646
|
+
border-radius: 4px;
|
|
7647
|
+
transition: all 0.15s ease;
|
|
7648
|
+
flex-shrink: 0;
|
|
7649
|
+
}
|
|
7650
|
+
.modal__close:hover {
|
|
7651
|
+
background: #f8f9fa;
|
|
7652
|
+
color: #212529;
|
|
7653
|
+
}
|
|
7654
|
+
@media (prefers-color-scheme: dark) {
|
|
7655
|
+
.modal__close:hover {
|
|
7656
|
+
background: #333;
|
|
7657
|
+
color: #fff;
|
|
7658
|
+
}
|
|
7659
|
+
}
|
|
7660
|
+
.modal__close:focus-visible {
|
|
7661
|
+
outline: 2px solid #009900;
|
|
7662
|
+
outline-offset: 2px;
|
|
7663
|
+
}
|
|
7664
|
+
.modal__close svg {
|
|
7665
|
+
display: block;
|
|
7666
|
+
}
|
|
7667
|
+
.modal__body {
|
|
7668
|
+
padding: 1.5rem;
|
|
7669
|
+
overflow-y: auto;
|
|
7670
|
+
flex: 1;
|
|
7671
|
+
color: #495057;
|
|
7672
|
+
}
|
|
7673
|
+
@media (prefers-color-scheme: dark) {
|
|
7674
|
+
.modal__body {
|
|
7675
|
+
color: #dee2e6;
|
|
7676
|
+
}
|
|
7677
|
+
}
|
|
7678
|
+
.modal__body::-webkit-scrollbar {
|
|
7679
|
+
width: 8px;
|
|
7680
|
+
}
|
|
7681
|
+
.modal__body::-webkit-scrollbar-track {
|
|
7682
|
+
background: #f8f9fa;
|
|
7683
|
+
border-radius: 4px;
|
|
7684
|
+
}
|
|
7685
|
+
@media (prefers-color-scheme: dark) {
|
|
7686
|
+
.modal__body::-webkit-scrollbar-track {
|
|
7687
|
+
background: #2a2a2a;
|
|
7688
|
+
}
|
|
7689
|
+
}
|
|
7690
|
+
.modal__body::-webkit-scrollbar-thumb {
|
|
7691
|
+
background: #ced4da;
|
|
7692
|
+
border-radius: 4px;
|
|
7693
|
+
}
|
|
7694
|
+
.modal__body::-webkit-scrollbar-thumb:hover {
|
|
7695
|
+
background: #adb5bd;
|
|
7696
|
+
}
|
|
7697
|
+
@media (prefers-color-scheme: dark) {
|
|
7698
|
+
.modal__body::-webkit-scrollbar-thumb {
|
|
7699
|
+
background: #555;
|
|
7700
|
+
}
|
|
7701
|
+
.modal__body::-webkit-scrollbar-thumb:hover {
|
|
7702
|
+
background: #666;
|
|
7703
|
+
}
|
|
7704
|
+
}
|
|
7705
|
+
.modal__footer {
|
|
7706
|
+
display: flex;
|
|
7707
|
+
align-items: center;
|
|
7708
|
+
justify-content: flex-end;
|
|
7709
|
+
gap: 0.75rem;
|
|
7710
|
+
padding: 1.5rem;
|
|
7711
|
+
border-top: 1px solid #e9ecef;
|
|
7712
|
+
flex-shrink: 0;
|
|
7713
|
+
}
|
|
7714
|
+
@media (prefers-color-scheme: dark) {
|
|
7715
|
+
.modal__footer {
|
|
7716
|
+
border-top-color: #333;
|
|
7717
|
+
}
|
|
7718
|
+
}
|
|
7719
|
+
|
|
7720
|
+
@keyframes fadeIn {
|
|
7721
|
+
from {
|
|
7722
|
+
opacity: 0;
|
|
7723
|
+
}
|
|
7724
|
+
to {
|
|
7725
|
+
opacity: 1;
|
|
7726
|
+
}
|
|
7727
|
+
}
|
|
7728
|
+
@keyframes slideIn {
|
|
7729
|
+
from {
|
|
7730
|
+
opacity: 0;
|
|
7731
|
+
transform: translateY(-20px) scale(0.95);
|
|
7732
|
+
}
|
|
7733
|
+
to {
|
|
7734
|
+
opacity: 1;
|
|
7735
|
+
transform: translateY(0) scale(1);
|
|
7736
|
+
}
|
|
7737
|
+
}
|
|
7738
|
+
@media (max-width: 640px) {
|
|
7739
|
+
.modal-overlay {
|
|
7740
|
+
padding: 0.5rem;
|
|
7741
|
+
}
|
|
7742
|
+
.modal {
|
|
7743
|
+
max-height: calc(100vh - 1rem);
|
|
7744
|
+
}
|
|
7745
|
+
.modal--sm, .modal--md, .modal--lg, .modal--xl {
|
|
7746
|
+
max-width: 100%;
|
|
7747
|
+
}
|
|
7748
|
+
.modal__header, .modal__body, .modal__footer {
|
|
7749
|
+
padding: 1rem;
|
|
7750
|
+
}
|
|
7751
|
+
.modal__title {
|
|
7752
|
+
font-size: 1.125rem;
|
|
7753
|
+
}
|
|
7754
|
+
}
|
|
7755
|
+
@media (prefers-contrast: high) {
|
|
7756
|
+
.modal {
|
|
7757
|
+
border: 2px solid currentColor;
|
|
7758
|
+
}
|
|
7759
|
+
.modal__header, .modal__footer {
|
|
7760
|
+
border-color: currentColor;
|
|
7761
|
+
}
|
|
7762
|
+
.modal__close:focus-visible {
|
|
7763
|
+
outline-width: 3px;
|
|
7764
|
+
}
|
|
7765
|
+
}
|
|
7548
7766
|
.logo {
|
|
7549
7767
|
width: 50%;
|
|
7550
7768
|
display: inline-flex;
|