elementdrawing 1.0.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/LICENSE +21 -0
- package/dist/elementdrawing.min.js +3 -0
- package/dist/elementdrawing.min.js.LICENSE.txt +8 -0
- package/dist/elementdrawing.min.js.map +1 -0
- package/dist/index.html +1 -0
- package/package.json +127 -0
- package/src/core/bridge.h +855 -0
- package/src/core/diff.c +900 -0
- package/src/core/element.c +1078 -0
- package/src/core/event.c +813 -0
- package/src/core/fiber.c +1027 -0
- package/src/core/hooks.c +919 -0
- package/src/core/renderer.c +963 -0
- package/src/core/scheduler.c +702 -0
- package/src/core/state.c +803 -0
- package/src/css/animations.css +779 -0
- package/src/css/base.css +615 -0
- package/src/css/components.css +1311 -0
- package/src/css/tailwind.css +370 -0
- package/src/css/themes.css +517 -0
- package/src/css/utilities.css +475 -0
- package/src/index.js +746 -0
- package/src/js/animation.js +655 -0
- package/src/js/dom.js +665 -0
- package/src/js/events.js +585 -0
- package/src/js/http.js +446 -0
- package/src/js/index.js +26 -0
- package/src/js/router.js +483 -0
- package/src/js/store.js +539 -0
- package/src/js/utils.js +593 -0
- package/src/js/validator.js +529 -0
- package/src/jsx/components/Accordion.jsx +210 -0
- package/src/jsx/components/Alert.jsx +169 -0
- package/src/jsx/components/Avatar.jsx +214 -0
- package/src/jsx/components/Badge.jsx +136 -0
- package/src/jsx/components/Breadcrumb.jsx +200 -0
- package/src/jsx/components/Button.jsx +188 -0
- package/src/jsx/components/Card.jsx +192 -0
- package/src/jsx/components/Carousel.jsx +278 -0
- package/src/jsx/components/Checkbox.jsx +215 -0
- package/src/jsx/components/Dialog.jsx +242 -0
- package/src/jsx/components/Drawer.jsx +190 -0
- package/src/jsx/components/Dropdown.jsx +268 -0
- package/src/jsx/components/Form.jsx +274 -0
- package/src/jsx/components/Input.jsx +285 -0
- package/src/jsx/components/Menu.jsx +276 -0
- package/src/jsx/components/Modal.jsx +274 -0
- package/src/jsx/components/Navbar.jsx +292 -0
- package/src/jsx/components/Pagination.jsx +268 -0
- package/src/jsx/components/Progress.jsx +252 -0
- package/src/jsx/components/Radio.jsx +208 -0
- package/src/jsx/components/Select.jsx +397 -0
- package/src/jsx/components/Sidebar.jsx +250 -0
- package/src/jsx/components/Slider.jsx +310 -0
- package/src/jsx/components/Spinner.jsx +198 -0
- package/src/jsx/components/Switch.jsx +201 -0
- package/src/jsx/components/Table.jsx +332 -0
- package/src/jsx/components/Tabs.jsx +227 -0
- package/src/jsx/components/Textarea.jsx +212 -0
- package/src/jsx/components/Toast.jsx +270 -0
- package/src/jsx/components/Tooltip.jsx +178 -0
- package/src/jsx/components/Typography.jsx +299 -0
- package/src/jsx/components/index.jsx +70 -0
- package/src/jsx/core/element.js +3 -0
- package/src/jsx/hooks/index.js +356 -0
- package/src/jsx/hooks/useCallback.js +472 -0
- package/src/jsx/hooks/useContext.js +586 -0
- package/src/jsx/hooks/useEffect.js +704 -0
- package/src/jsx/hooks/useLayoutEffect.js +508 -0
- package/src/jsx/hooks/useMemo.js +689 -0
- package/src/jsx/hooks/useReducer.js +729 -0
- package/src/jsx/hooks/useRef.js +542 -0
- package/src/jsx/hooks/useState.js +854 -0
- package/src/jsx/runtime/commit.js +903 -0
- package/src/jsx/runtime/createElement.js +860 -0
- package/src/jsx/runtime/index.js +356 -0
- package/src/jsx/runtime/reconcile.js +687 -0
- package/src/jsx/runtime/render.js +914 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dialog Component for ElementDrawing Framework
|
|
3
|
+
* Supports confirmation, alert, prompt dialogs, custom content, keyboard navigation, focus trap
|
|
4
|
+
*/
|
|
5
|
+
const ED = require('../core/element');
|
|
6
|
+
|
|
7
|
+
const DIALOG_TYPES = {
|
|
8
|
+
confirm: {
|
|
9
|
+
icon: ED.createElement('svg', {
|
|
10
|
+
className: 'ed-w-6 ed-h-6 ed-text-blue-600',
|
|
11
|
+
fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor',
|
|
12
|
+
children: ED.createElement('path', { strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: 2, d: 'M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z' }),
|
|
13
|
+
}),
|
|
14
|
+
iconBg: 'ed-bg-blue-100',
|
|
15
|
+
},
|
|
16
|
+
alert: {
|
|
17
|
+
icon: ED.createElement('svg', {
|
|
18
|
+
className: 'ed-w-6 ed-h-6 ed-text-red-600',
|
|
19
|
+
fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor',
|
|
20
|
+
children: ED.createElement('path', { strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: 2, d: 'M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z' }),
|
|
21
|
+
}),
|
|
22
|
+
iconBg: 'ed-bg-red-100',
|
|
23
|
+
},
|
|
24
|
+
prompt: {
|
|
25
|
+
icon: ED.createElement('svg', {
|
|
26
|
+
className: 'ed-w-6 ed-h-6 ed-text-green-600',
|
|
27
|
+
fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor',
|
|
28
|
+
children: ED.createElement('path', { strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: 2, d: 'M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z' }),
|
|
29
|
+
}),
|
|
30
|
+
iconBg: 'ed-bg-green-100',
|
|
31
|
+
},
|
|
32
|
+
info: {
|
|
33
|
+
icon: ED.createElement('svg', {
|
|
34
|
+
className: 'ed-w-6 ed-h-6 ed-text-blue-600',
|
|
35
|
+
fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor',
|
|
36
|
+
children: ED.createElement('path', { strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: 2, d: 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z' }),
|
|
37
|
+
}),
|
|
38
|
+
iconBg: 'ed-bg-blue-100',
|
|
39
|
+
},
|
|
40
|
+
warning: {
|
|
41
|
+
icon: ED.createElement('svg', {
|
|
42
|
+
className: 'ed-w-6 ed-h-6 ed-text-yellow-600',
|
|
43
|
+
fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor',
|
|
44
|
+
children: ED.createElement('path', { strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: 2, d: 'M12 9v2m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z' }),
|
|
45
|
+
}),
|
|
46
|
+
iconBg: 'ed-bg-yellow-100',
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function Dialog(props) {
|
|
51
|
+
const {
|
|
52
|
+
visible = false,
|
|
53
|
+
type = 'confirm',
|
|
54
|
+
title,
|
|
55
|
+
content,
|
|
56
|
+
children,
|
|
57
|
+
icon,
|
|
58
|
+
okText = 'OK',
|
|
59
|
+
cancelText = 'Cancel',
|
|
60
|
+
onOk,
|
|
61
|
+
onCancel,
|
|
62
|
+
onClose,
|
|
63
|
+
confirmLoading = false,
|
|
64
|
+
showCancel = true,
|
|
65
|
+
closable = true,
|
|
66
|
+
closeOnBackdrop = true,
|
|
67
|
+
closeOnEscape = true,
|
|
68
|
+
backdrop = true,
|
|
69
|
+
className = '',
|
|
70
|
+
style = {},
|
|
71
|
+
width = 420,
|
|
72
|
+
footer,
|
|
73
|
+
header,
|
|
74
|
+
inputProps,
|
|
75
|
+
inputValue,
|
|
76
|
+
onInputChange,
|
|
77
|
+
okButtonProps,
|
|
78
|
+
cancelButtonProps,
|
|
79
|
+
danger = false,
|
|
80
|
+
autoFocus = true,
|
|
81
|
+
focusTrap = true,
|
|
82
|
+
zIndex = 1000,
|
|
83
|
+
afterClose,
|
|
84
|
+
keyboard = true,
|
|
85
|
+
maskClosable = true,
|
|
86
|
+
centered = true,
|
|
87
|
+
} = props;
|
|
88
|
+
|
|
89
|
+
if (!visible) return null;
|
|
90
|
+
|
|
91
|
+
const dialogType = DIALOG_TYPES[type] || DIALOG_TYPES.confirm;
|
|
92
|
+
|
|
93
|
+
const handleBackdropClick = (e) => {
|
|
94
|
+
if ((closeOnBackdrop || maskClosable) && e.target === e.currentTarget) {
|
|
95
|
+
onClose?.();
|
|
96
|
+
onCancel?.();
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const handleKeyDown = (e) => {
|
|
101
|
+
if (!keyboard) return;
|
|
102
|
+
if (closeOnEscape && e.key === 'Escape') {
|
|
103
|
+
onClose?.();
|
|
104
|
+
onCancel?.();
|
|
105
|
+
}
|
|
106
|
+
if (focusTrap) {
|
|
107
|
+
if (e.key === 'Tab') {
|
|
108
|
+
const dialogEl = e.currentTarget;
|
|
109
|
+
const focusable = dialogEl.querySelectorAll(
|
|
110
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
111
|
+
);
|
|
112
|
+
if (focusable.length > 0) {
|
|
113
|
+
const first = focusable[0];
|
|
114
|
+
const last = focusable[focusable.length - 1];
|
|
115
|
+
if (e.shiftKey && document.activeElement === first) {
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
last.focus();
|
|
118
|
+
} else if (!e.shiftKey && document.activeElement === last) {
|
|
119
|
+
e.preventDefault();
|
|
120
|
+
first.focus();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const iconElement = icon !== undefined ? icon
|
|
128
|
+
: ED.createElement('div', {
|
|
129
|
+
className: `ed-w-12 ed-h-12 ed-rounded-full ed-flex ed-items-center ed-justify-center ed-mx-auto ed-mb-4 ${dialogType.iconBg}`,
|
|
130
|
+
children: dialogType.icon,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const titleElement = title
|
|
134
|
+
? ED.createElement('h3', {
|
|
135
|
+
className: 'ed-text-lg ed-font-semibold ed-text-gray-900 ed-text-center ed-mb-2',
|
|
136
|
+
}, title)
|
|
137
|
+
: null;
|
|
138
|
+
|
|
139
|
+
const contentElement = content || children
|
|
140
|
+
? ED.createElement('div', {
|
|
141
|
+
className: 'ed-text-sm ed-text-gray-500 ed-text-center ed-mb-6',
|
|
142
|
+
}, content || children)
|
|
143
|
+
: null;
|
|
144
|
+
|
|
145
|
+
const promptInput = type === 'prompt'
|
|
146
|
+
? ED.createElement('div', { className: 'ed-mb-6' },
|
|
147
|
+
ED.createElement('input', {
|
|
148
|
+
className: 'ed-w-full ed-px-3 ed-py-2 ed-border ed-border-gray-300 ed-rounded-md ed-text-sm focus:ed-outline-none focus:ed-ring-2 focus:ed-ring-blue-500 focus:ed-border-transparent',
|
|
149
|
+
value: inputValue,
|
|
150
|
+
onChange: onInputChange,
|
|
151
|
+
autoFocus,
|
|
152
|
+
...inputProps,
|
|
153
|
+
})
|
|
154
|
+
)
|
|
155
|
+
: null;
|
|
156
|
+
|
|
157
|
+
const cancelButton = showCancel
|
|
158
|
+
? ED.createElement('button', {
|
|
159
|
+
key: 'cancel',
|
|
160
|
+
className: 'ed-px-4 ed-py-2 ed-text-sm ed-font-medium ed-text-gray-700 ed-bg-white ed-border ed-border-gray-300 ed-rounded-md hover:ed-bg-gray-50 focus:ed-outline-none focus:ed-ring-2 focus:ed-ring-offset-2 focus:ed-ring-gray-300 ed-transition-colors',
|
|
161
|
+
onClick: onCancel || onClose,
|
|
162
|
+
...cancelButtonProps,
|
|
163
|
+
}, cancelText)
|
|
164
|
+
: null;
|
|
165
|
+
|
|
166
|
+
const okButtonClass = danger
|
|
167
|
+
? 'ed-bg-red-600 hover:ed-bg-red-700 focus:ed-ring-red-500'
|
|
168
|
+
: 'ed-bg-blue-600 hover:ed-bg-blue-700 focus:ed-ring-blue-500';
|
|
169
|
+
|
|
170
|
+
const okButton = ED.createElement('button', {
|
|
171
|
+
key: 'ok',
|
|
172
|
+
className: [
|
|
173
|
+
'ed-px-4 ed-py-2 ed-text-sm ed-font-medium ed-text-white ed-rounded-md focus:ed-outline-none focus:ed-ring-2 focus:ed-ring-offset-2 ed-transition-colors',
|
|
174
|
+
okButtonClass,
|
|
175
|
+
confirmLoading ? 'ed-opacity-70 ed-cursor-not-allowed' : '',
|
|
176
|
+
].filter(Boolean).join(' '),
|
|
177
|
+
onClick: onOk,
|
|
178
|
+
disabled: confirmLoading,
|
|
179
|
+
autoFocus: type !== 'prompt' && autoFocus,
|
|
180
|
+
...okButtonProps,
|
|
181
|
+
children: confirmLoading
|
|
182
|
+
? ED.createElement('span', { className: 'ed-flex ed-items-center ed-gap-2' },
|
|
183
|
+
ED.createElement('span', { className: 'ed-w-4 ed-h-4 ed-border-2 ed-border-white ed-border-t-transparent ed-rounded-full ed-animate-spin' }),
|
|
184
|
+
okText
|
|
185
|
+
)
|
|
186
|
+
: okText,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const footerElement = footer !== undefined ? footer
|
|
190
|
+
: ED.createElement('div', {
|
|
191
|
+
className: 'ed-flex ed-items-center ed-justify-center ed-gap-3',
|
|
192
|
+
}, [cancelButton, okButton].filter(Boolean));
|
|
193
|
+
|
|
194
|
+
return ED.createElement('div', {
|
|
195
|
+
className: [
|
|
196
|
+
'ed-fixed ed-inset-0 ed-flex ed-z-50',
|
|
197
|
+
centered ? 'ed-items-center ed-justify-center' : 'ed-items-start ed-justify-center ed-pt-24',
|
|
198
|
+
backdrop ? '' : '',
|
|
199
|
+
'ed-dialog-backdrop',
|
|
200
|
+
].filter(Boolean).join(' '),
|
|
201
|
+
style: { zIndex },
|
|
202
|
+
onClick: handleBackdropClick,
|
|
203
|
+
onKeyDown: handleKeyDown,
|
|
204
|
+
children: ED.createElement('div', {
|
|
205
|
+
className: [
|
|
206
|
+
'ed-bg-white ed-rounded-xl ed-shadow-2xl ed-p-6 ed-w-full',
|
|
207
|
+
'ed-transform ed-transition-all ed-duration-200',
|
|
208
|
+
'ed-animate-zoom-in',
|
|
209
|
+
className,
|
|
210
|
+
].filter(Boolean).join(' '),
|
|
211
|
+
style: { maxWidth: width, ...style },
|
|
212
|
+
role: 'dialog',
|
|
213
|
+
'aria-modal': 'true',
|
|
214
|
+
'aria-labelledby': title ? 'dialog-title' : undefined,
|
|
215
|
+
children: [
|
|
216
|
+
closable
|
|
217
|
+
? ED.createElement('button', {
|
|
218
|
+
key: 'close',
|
|
219
|
+
className: 'ed-absolute ed-top-3 ed-right-3 ed-p-1 ed-rounded-md ed-text-gray-400 hover:ed-text-gray-600 hover:ed-bg-gray-100 ed-transition-colors',
|
|
220
|
+
onClick: onClose,
|
|
221
|
+
'aria-label': 'Close dialog',
|
|
222
|
+
children: ED.createElement('svg', {
|
|
223
|
+
className: 'ed-w-5 ed-h-5',
|
|
224
|
+
fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor',
|
|
225
|
+
children: ED.createElement('path', { strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: 2, d: 'M6 18L18 6M6 6l12 12' }),
|
|
226
|
+
}),
|
|
227
|
+
})
|
|
228
|
+
: null,
|
|
229
|
+
iconElement,
|
|
230
|
+
titleElement,
|
|
231
|
+
contentElement,
|
|
232
|
+
promptInput,
|
|
233
|
+
footerElement,
|
|
234
|
+
].filter(Boolean),
|
|
235
|
+
}),
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
Dialog.displayName = 'Dialog';
|
|
240
|
+
Dialog.TYPES = DIALOG_TYPES;
|
|
241
|
+
|
|
242
|
+
module.exports = Dialog;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drawer Component for ElementDrawing Framework
|
|
3
|
+
* Supports positions, sizes, overlay, close on escape, nested drawers, resizable
|
|
4
|
+
*/
|
|
5
|
+
const ED = require('../core/element');
|
|
6
|
+
|
|
7
|
+
const DRAWER_POSITIONS = {
|
|
8
|
+
left: {
|
|
9
|
+
container: 'ed-left-0 ed-top-0 ed-bottom-0 ed-h-full',
|
|
10
|
+
animation: 'ed-animate-slide-in-left',
|
|
11
|
+
defaultSize: { width: 400 },
|
|
12
|
+
resizeClass: 'ed-right-0 ed-top-0 ed-bottom-0 ed-w-1 ed-cursor-ew-resize',
|
|
13
|
+
},
|
|
14
|
+
right: {
|
|
15
|
+
container: 'ed-right-0 ed-top-0 ed-bottom-0 ed-h-full',
|
|
16
|
+
animation: 'ed-animate-slide-in-right',
|
|
17
|
+
defaultSize: { width: 400 },
|
|
18
|
+
resizeClass: 'ed-left-0 ed-top-0 ed-bottom-0 ed-w-1 ed-cursor-ew-resize',
|
|
19
|
+
},
|
|
20
|
+
top: {
|
|
21
|
+
container: 'ed-top-0 ed-left-0 ed-right-0 ed-w-full',
|
|
22
|
+
animation: 'ed-animate-slide-in-top',
|
|
23
|
+
defaultSize: { height: 300 },
|
|
24
|
+
resizeClass: 'ed-bottom-0 ed-left-0 ed-right-0 ed-h-1 ed-cursor-ns-resize',
|
|
25
|
+
},
|
|
26
|
+
bottom: {
|
|
27
|
+
container: 'ed-bottom-0 ed-left-0 ed-right-0 ed-w-full',
|
|
28
|
+
animation: 'ed-animate-slide-in-bottom',
|
|
29
|
+
defaultSize: { height: 300 },
|
|
30
|
+
resizeClass: 'ed-top-0 ed-left-0 ed-right-0 ed-h-1 ed-cursor-ns-resize',
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const DRAWER_SIZES = {
|
|
35
|
+
sm: { width: 300, height: 200 },
|
|
36
|
+
md: { width: 400, height: 300 },
|
|
37
|
+
lg: { width: 600, height: 450 },
|
|
38
|
+
xl: { width: 800, height: 600 },
|
|
39
|
+
full: { width: '100%', height: '100%' },
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function Drawer(props) {
|
|
43
|
+
const {
|
|
44
|
+
visible = false,
|
|
45
|
+
position = 'right',
|
|
46
|
+
size = 'md',
|
|
47
|
+
width,
|
|
48
|
+
height,
|
|
49
|
+
overlay = true,
|
|
50
|
+
overlayOpacity = 0.5,
|
|
51
|
+
closeOnEscape = true,
|
|
52
|
+
closeOnOverlay = true,
|
|
53
|
+
closable = true,
|
|
54
|
+
onClose,
|
|
55
|
+
title,
|
|
56
|
+
children,
|
|
57
|
+
footer,
|
|
58
|
+
className = '',
|
|
59
|
+
style = {},
|
|
60
|
+
zIndex = 1000,
|
|
61
|
+
headerStyle = {},
|
|
62
|
+
bodyStyle = {},
|
|
63
|
+
footerStyle = {},
|
|
64
|
+
resizable = false,
|
|
65
|
+
nested = false,
|
|
66
|
+
destroyOnClose = true,
|
|
67
|
+
placement = position,
|
|
68
|
+
mask = true,
|
|
69
|
+
maskClosable = true,
|
|
70
|
+
afterOpenChange,
|
|
71
|
+
extra,
|
|
72
|
+
width: widthProp,
|
|
73
|
+
} = props;
|
|
74
|
+
|
|
75
|
+
if (!visible && destroyOnClose) return null;
|
|
76
|
+
if (!visible) return null;
|
|
77
|
+
|
|
78
|
+
const pos = DRAWER_POSITIONS[placement || position] || DRAWER_POSITIONS.right;
|
|
79
|
+
const sizeConfig = DRAWER_SIZES[size] || DRAWER_SIZES.md;
|
|
80
|
+
const drawerWidth = widthProp || width || sizeConfig.width;
|
|
81
|
+
const drawerHeight = height || sizeConfig.height;
|
|
82
|
+
|
|
83
|
+
const isHorizontal = placement === 'left' || placement === 'right';
|
|
84
|
+
|
|
85
|
+
const drawerClasses = [
|
|
86
|
+
'ed-fixed ed-bg-white ed-shadow-xl ed-flex ed-flex-col ed-z-50',
|
|
87
|
+
pos.container,
|
|
88
|
+
'ed-transition-transform ed-duration-300',
|
|
89
|
+
className,
|
|
90
|
+
].filter(Boolean).join(' ');
|
|
91
|
+
|
|
92
|
+
const drawerStyle = {
|
|
93
|
+
[isHorizontal ? 'width' : 'height']: drawerWidth || drawerHeight,
|
|
94
|
+
...style,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const handleOverlayClick = () => {
|
|
98
|
+
if (closeOnOverlay || maskClosable) onClose?.();
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const handleKeyDown = (e) => {
|
|
102
|
+
if (closeOnEscape && e.key === 'Escape') onClose?.();
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const headerElement = title || closable || extra
|
|
106
|
+
? ED.createElement('div', {
|
|
107
|
+
className: 'ed-flex ed-items-center ed-justify-between ed-px-6 ed-py-4 ed-border-b ed-border-gray-200 ed-flex-shrink-0',
|
|
108
|
+
style: headerStyle,
|
|
109
|
+
children: [
|
|
110
|
+
ED.createElement('div', { key: 'title-area', className: 'ed-flex ed-items-center ed-gap-3' },
|
|
111
|
+
title
|
|
112
|
+
? ED.createElement('h3', { className: 'ed-text-lg ed-font-semibold ed-text-gray-900' }, title)
|
|
113
|
+
: null,
|
|
114
|
+
extra
|
|
115
|
+
? ED.createElement('div', { className: 'ed-text-sm ed-text-gray-500' }, extra)
|
|
116
|
+
: null
|
|
117
|
+
),
|
|
118
|
+
closable
|
|
119
|
+
? ED.createElement('button', {
|
|
120
|
+
key: 'close',
|
|
121
|
+
className: 'ed-p-1.5 ed-rounded-md ed-text-gray-400 hover:ed-text-gray-600 hover:ed-bg-gray-100 ed-transition-colors',
|
|
122
|
+
onClick: onClose,
|
|
123
|
+
'aria-label': 'Close drawer',
|
|
124
|
+
children: ED.createElement('svg', {
|
|
125
|
+
className: 'ed-w-5 ed-h-5',
|
|
126
|
+
fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor',
|
|
127
|
+
children: ED.createElement('path', { strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: 2, d: 'M6 18L18 6M6 6l12 12' }),
|
|
128
|
+
}),
|
|
129
|
+
})
|
|
130
|
+
: null,
|
|
131
|
+
].filter(Boolean),
|
|
132
|
+
})
|
|
133
|
+
: null;
|
|
134
|
+
|
|
135
|
+
const bodyElement = ED.createElement('div', {
|
|
136
|
+
className: 'ed-flex-1 ed-overflow-y-auto ed-px-6 ed-py-4',
|
|
137
|
+
style: bodyStyle,
|
|
138
|
+
children,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const footerElement = footer
|
|
142
|
+
? ED.createElement('div', {
|
|
143
|
+
className: 'ed-px-6 ed-py-3 ed-border-t ed-border-gray-200 ed-flex ed-items-center ed-justify-end ed-gap-3 ed-flex-shrink-0',
|
|
144
|
+
style: footerStyle,
|
|
145
|
+
children: footer,
|
|
146
|
+
})
|
|
147
|
+
: null;
|
|
148
|
+
|
|
149
|
+
const resizeHandle = resizable
|
|
150
|
+
? ED.createElement('div', {
|
|
151
|
+
className: [
|
|
152
|
+
'ed-absolute ed-bg-transparent ed-hover:ed-bg-blue-200 ed-transition-colors ed-z-10',
|
|
153
|
+
pos.resizeClass,
|
|
154
|
+
].join(' '),
|
|
155
|
+
})
|
|
156
|
+
: null;
|
|
157
|
+
|
|
158
|
+
const overlayElement = (overlay || mask)
|
|
159
|
+
? ED.createElement('div', {
|
|
160
|
+
className: [
|
|
161
|
+
'ed-fixed ed-inset-0 ed-bg-black ed-transition-opacity ed-duration-300',
|
|
162
|
+
nested ? '' : '',
|
|
163
|
+
].join(' '),
|
|
164
|
+
style: {
|
|
165
|
+
zIndex: zIndex - 1,
|
|
166
|
+
opacity: overlayOpacity,
|
|
167
|
+
},
|
|
168
|
+
onClick: handleOverlayClick,
|
|
169
|
+
})
|
|
170
|
+
: null;
|
|
171
|
+
|
|
172
|
+
return ED.createElement(ED.Fragment, null, [
|
|
173
|
+
overlayElement,
|
|
174
|
+
ED.createElement('div', {
|
|
175
|
+
key: 'drawer',
|
|
176
|
+
className: drawerClasses,
|
|
177
|
+
style: { ...drawerStyle, zIndex },
|
|
178
|
+
onKeyDown: handleKeyDown,
|
|
179
|
+
role: 'dialog',
|
|
180
|
+
'aria-modal': 'true',
|
|
181
|
+
children: [headerElement, bodyElement, footerElement, resizeHandle].filter(Boolean),
|
|
182
|
+
}),
|
|
183
|
+
]);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
Drawer.displayName = 'Drawer';
|
|
187
|
+
Drawer.POSITIONS = DRAWER_POSITIONS;
|
|
188
|
+
Drawer.SIZES = DRAWER_SIZES;
|
|
189
|
+
|
|
190
|
+
module.exports = Drawer;
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dropdown Component for ElementDrawing Framework
|
|
3
|
+
* Supports triggers, items, dividers, icons, submenus, keyboard navigation, search
|
|
4
|
+
*/
|
|
5
|
+
const ED = require('../core/element');
|
|
6
|
+
|
|
7
|
+
function DropdownItem(props) {
|
|
8
|
+
const {
|
|
9
|
+
children,
|
|
10
|
+
label,
|
|
11
|
+
icon,
|
|
12
|
+
disabled = false,
|
|
13
|
+
active = false,
|
|
14
|
+
danger = false,
|
|
15
|
+
shortcut,
|
|
16
|
+
href,
|
|
17
|
+
onClick,
|
|
18
|
+
className = '',
|
|
19
|
+
style = {},
|
|
20
|
+
divided = false,
|
|
21
|
+
selected = false,
|
|
22
|
+
iconRight,
|
|
23
|
+
description,
|
|
24
|
+
} = props;
|
|
25
|
+
|
|
26
|
+
if (divided) {
|
|
27
|
+
return ED.createElement('div', {
|
|
28
|
+
className: 'ed-my-1 ed-border-t ed-border-gray-100',
|
|
29
|
+
role: 'separator',
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const itemClasses = [
|
|
34
|
+
'ed-flex ed-items-center ed-px-3 ed-py-2 ed-text-sm ed-transition-colors ed-duration-150 ed-gap-2',
|
|
35
|
+
disabled
|
|
36
|
+
? 'ed-text-gray-400 ed-cursor-not-allowed'
|
|
37
|
+
: danger
|
|
38
|
+
? 'ed-text-red-600 hover:ed-bg-red-50 ed-cursor-pointer'
|
|
39
|
+
: active || selected
|
|
40
|
+
? 'ed-bg-blue-50 ed-text-blue-700 ed-cursor-pointer'
|
|
41
|
+
: 'ed-text-gray-700 hover:ed-bg-gray-50 ed-cursor-pointer',
|
|
42
|
+
className,
|
|
43
|
+
].filter(Boolean).join(' ');
|
|
44
|
+
|
|
45
|
+
const iconElement = icon
|
|
46
|
+
? ED.createElement('span', {
|
|
47
|
+
className: `ed-w-4 ed-h-4 ed-flex ed-items-center ed-justify-center ed-flex-shrink-0 ${disabled ? 'ed-text-gray-300' : ''}`,
|
|
48
|
+
children: typeof icon === 'string' ? ED.createElement('i', { className: icon }) : icon,
|
|
49
|
+
})
|
|
50
|
+
: null;
|
|
51
|
+
|
|
52
|
+
const shortcutElement = shortcut
|
|
53
|
+
? ED.createElement('span', {
|
|
54
|
+
className: 'ed-ml-auto ed-text-xs ed-text-gray-400 ed-font-mono',
|
|
55
|
+
}, shortcut)
|
|
56
|
+
: null;
|
|
57
|
+
|
|
58
|
+
const iconRightElement = iconRight
|
|
59
|
+
? ED.createElement('span', {
|
|
60
|
+
className: 'ed-ml-auto ed-w-4 ed-h-4 ed-flex ed-items-center ed-justify-center ed-text-gray-400',
|
|
61
|
+
children: typeof iconRight === 'string' ? ED.createElement('i', { className: iconRight }) : iconRight,
|
|
62
|
+
})
|
|
63
|
+
: null;
|
|
64
|
+
|
|
65
|
+
const labelElement = label || children;
|
|
66
|
+
const descriptionElement = description
|
|
67
|
+
? ED.createElement('span', { className: 'ed-text-xs ed-text-gray-400 ed-block' }, description)
|
|
68
|
+
: null;
|
|
69
|
+
|
|
70
|
+
const contentArea = ED.createElement('div', { className: 'ed-flex-1 ed-min-w-0' }, [
|
|
71
|
+
ED.createElement('span', { key: 'label', className: 'ed-truncate ed-block' }, labelElement),
|
|
72
|
+
descriptionElement,
|
|
73
|
+
].filter(Boolean));
|
|
74
|
+
|
|
75
|
+
if (href && !disabled) {
|
|
76
|
+
return ED.createElement('a', {
|
|
77
|
+
href,
|
|
78
|
+
className: itemClasses,
|
|
79
|
+
style,
|
|
80
|
+
onClick,
|
|
81
|
+
role: 'menuitem',
|
|
82
|
+
tabIndex: disabled ? -1 : 0,
|
|
83
|
+
children: [iconElement, contentArea, shortcutElement, iconRightElement].filter(Boolean),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return ED.createElement('div', {
|
|
88
|
+
className: itemClasses,
|
|
89
|
+
style,
|
|
90
|
+
onClick: disabled ? undefined : onClick,
|
|
91
|
+
role: 'menuitem',
|
|
92
|
+
tabIndex: disabled ? -1 : 0,
|
|
93
|
+
'aria-disabled': disabled,
|
|
94
|
+
onKeyDown: (e) => {
|
|
95
|
+
if (e.key === 'Enter' && !disabled) onClick?.(e);
|
|
96
|
+
},
|
|
97
|
+
children: [iconElement, contentArea, shortcutElement, iconRightElement].filter(Boolean),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
DropdownItem.displayName = 'DropdownItem';
|
|
102
|
+
|
|
103
|
+
function Dropdown(props) {
|
|
104
|
+
const {
|
|
105
|
+
children,
|
|
106
|
+
items = [],
|
|
107
|
+
trigger = 'click',
|
|
108
|
+
placement = 'bottom-start',
|
|
109
|
+
disabled = false,
|
|
110
|
+
className = '',
|
|
111
|
+
style = {},
|
|
112
|
+
menuClassName = '',
|
|
113
|
+
menuStyle = {},
|
|
114
|
+
open,
|
|
115
|
+
defaultOpen = false,
|
|
116
|
+
onOpenChange,
|
|
117
|
+
onSelect,
|
|
118
|
+
searchable = false,
|
|
119
|
+
searchPlaceholder = 'Search...',
|
|
120
|
+
onSearch,
|
|
121
|
+
searchValue,
|
|
122
|
+
emptyText = 'No items',
|
|
123
|
+
maxHeight = 300,
|
|
124
|
+
width,
|
|
125
|
+
arrow = false,
|
|
126
|
+
offset = 4,
|
|
127
|
+
closeOnSelect = true,
|
|
128
|
+
closeOnOutsideClick = true,
|
|
129
|
+
overlay,
|
|
130
|
+
renderMenu,
|
|
131
|
+
} = props;
|
|
132
|
+
|
|
133
|
+
const isOpen = open !== undefined ? open : defaultOpen;
|
|
134
|
+
|
|
135
|
+
const placementClasses = {
|
|
136
|
+
'bottom-start': 'ed-top-full ed-left-0',
|
|
137
|
+
'bottom': 'ed-top-full ed-left-1/2 ed--translate-x-1/2',
|
|
138
|
+
'bottom-end': 'ed-top-full ed-right-0',
|
|
139
|
+
'top-start': 'ed-bottom-full ed-left-0',
|
|
140
|
+
'top': 'ed-bottom-full ed-left-1/2 ed--translate-x-1/2',
|
|
141
|
+
'top-end': 'ed-bottom-full ed-right-0',
|
|
142
|
+
'left-start': 'ed-right-full ed-top-0',
|
|
143
|
+
'left': 'ed-right-full ed-top-1/2 ed--translate-y-1/2',
|
|
144
|
+
'left-end': 'ed-right-full ed-bottom-0',
|
|
145
|
+
'right-start': 'ed-left-full ed-top-0',
|
|
146
|
+
'right': 'ed-left-full ed-top-1/2 ed--translate-y-1/2',
|
|
147
|
+
'right-end': 'ed-left-full ed-bottom-0',
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const handleToggle = () => {
|
|
151
|
+
if (disabled) return;
|
|
152
|
+
onOpenChange?.(!isOpen);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const handleItemClick = (item) => {
|
|
156
|
+
if (item.disabled) return;
|
|
157
|
+
onSelect?.(item);
|
|
158
|
+
if (closeOnSelect) onOpenChange?.(false);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const handleKeyDown = (e) => {
|
|
162
|
+
if (e.key === 'Escape') onOpenChange?.(false);
|
|
163
|
+
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
164
|
+
e.preventDefault();
|
|
165
|
+
const menuEl = e.currentTarget.querySelector('[role="menu"]');
|
|
166
|
+
if (menuEl) {
|
|
167
|
+
const focusable = menuEl.querySelectorAll('[role="menuitem"]:not([aria-disabled="true"])');
|
|
168
|
+
const currentIdx = Array.from(focusable).indexOf(document.activeElement);
|
|
169
|
+
const nextIdx = e.key === 'ArrowDown'
|
|
170
|
+
? Math.min(currentIdx + 1, focusable.length - 1)
|
|
171
|
+
: Math.max(currentIdx - 1, 0);
|
|
172
|
+
focusable[nextIdx]?.focus();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const searchElement = searchable
|
|
178
|
+
? ED.createElement('div', { className: 'ed-px-2 ed-py-2 ed-border-b ed-border-gray-100' },
|
|
179
|
+
ED.createElement('input', {
|
|
180
|
+
className: 'ed-w-full ed-px-2 ed-py-1.5 ed-text-sm ed-border ed-border-gray-200 ed-rounded ed-outline-none focus:ed-ring-1 focus:ed-ring-blue-500',
|
|
181
|
+
placeholder: searchPlaceholder,
|
|
182
|
+
value: searchValue,
|
|
183
|
+
onChange: (e) => onSearch?.(e.target.value),
|
|
184
|
+
autoFocus: true,
|
|
185
|
+
})
|
|
186
|
+
)
|
|
187
|
+
: null;
|
|
188
|
+
|
|
189
|
+
const menuItems = items.length > 0
|
|
190
|
+
? items.map((item, idx) =>
|
|
191
|
+
ED.createElement(DropdownItem, {
|
|
192
|
+
key: item.key || idx,
|
|
193
|
+
label: item.label,
|
|
194
|
+
icon: item.icon,
|
|
195
|
+
disabled: item.disabled,
|
|
196
|
+
active: item.active,
|
|
197
|
+
danger: item.danger,
|
|
198
|
+
shortcut: item.shortcut,
|
|
199
|
+
href: item.href,
|
|
200
|
+
divided: item.divided,
|
|
201
|
+
selected: item.selected,
|
|
202
|
+
iconRight: item.iconRight,
|
|
203
|
+
description: item.description,
|
|
204
|
+
onClick: () => handleItemClick(item),
|
|
205
|
+
})
|
|
206
|
+
)
|
|
207
|
+
: children;
|
|
208
|
+
|
|
209
|
+
const menuContent = renderMenu
|
|
210
|
+
? renderMenu(items)
|
|
211
|
+
: ED.createElement('div', {
|
|
212
|
+
className: [
|
|
213
|
+
'ed-absolute ed-bg-white ed-rounded-lg ed-shadow-lg ed-border ed-border-gray-200 ed-z-50',
|
|
214
|
+
'ed-min-w-[180px] ed-py-1',
|
|
215
|
+
placementClasses[placement] || placementClasses['bottom-start'],
|
|
216
|
+
arrow ? 'ed-mt-2' : '',
|
|
217
|
+
menuClassName,
|
|
218
|
+
].filter(Boolean).join(' '),
|
|
219
|
+
style: {
|
|
220
|
+
maxHeight,
|
|
221
|
+
overflowY: 'auto',
|
|
222
|
+
width,
|
|
223
|
+
...menuStyle,
|
|
224
|
+
},
|
|
225
|
+
role: 'menu',
|
|
226
|
+
children: [
|
|
227
|
+
searchElement,
|
|
228
|
+
menuItems?.length > 0 || children
|
|
229
|
+
? menuItems
|
|
230
|
+
: ED.createElement('div', { className: 'ed-px-3 ed-py-4 ed-text-center ed-text-sm ed-text-gray-400' }, emptyText),
|
|
231
|
+
].filter(Boolean),
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const triggerEvents = {};
|
|
235
|
+
if (trigger === 'click') {
|
|
236
|
+
triggerEvents.onClick = handleToggle;
|
|
237
|
+
} else if (trigger === 'hover') {
|
|
238
|
+
triggerEvents.onMouseEnter = () => onOpenChange?.(true);
|
|
239
|
+
triggerEvents.onMouseLeave = () => onOpenChange?.(false);
|
|
240
|
+
} else if (trigger === 'context') {
|
|
241
|
+
triggerEvents.onContextMenu = (e) => { e.preventDefault(); handleToggle(); };
|
|
242
|
+
} else if (trigger === 'focus') {
|
|
243
|
+
triggerEvents.onFocus = () => onOpenChange?.(true);
|
|
244
|
+
triggerEvents.onBlur = () => onOpenChange?.(false);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return ED.createElement('div', {
|
|
248
|
+
className: `ed-relative ed-inline-flex ${className}`,
|
|
249
|
+
style,
|
|
250
|
+
onKeyDown: handleKeyDown,
|
|
251
|
+
...triggerEvents,
|
|
252
|
+
children: [
|
|
253
|
+
ED.createElement('div', {
|
|
254
|
+
key: 'trigger',
|
|
255
|
+
className: 'ed-dropdown-trigger ed-inline-flex',
|
|
256
|
+
'aria-haspopup': 'true',
|
|
257
|
+
'aria-expanded': isOpen,
|
|
258
|
+
children,
|
|
259
|
+
}),
|
|
260
|
+
isOpen ? menuContent : null,
|
|
261
|
+
],
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
Dropdown.displayName = 'Dropdown';
|
|
266
|
+
Dropdown.Item = DropdownItem;
|
|
267
|
+
|
|
268
|
+
module.exports = Dropdown;
|