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,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slider Component for ElementDrawing Framework
|
|
3
|
+
* Supports single/double handle, marks, tooltip, range, vertical, disabled, custom step, log scale
|
|
4
|
+
*/
|
|
5
|
+
const ED = require('../core/element');
|
|
6
|
+
|
|
7
|
+
const SLIDER_SIZES = {
|
|
8
|
+
sm: { trackHeight: 4, handleSize: 14, markFont: 'ed-text-xs' },
|
|
9
|
+
md: { trackHeight: 6, handleSize: 18, markFont: 'ed-text-xs' },
|
|
10
|
+
lg: { trackHeight: 8, handleSize: 22, markFont: 'ed-text-sm' },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function Slider(props) {
|
|
14
|
+
const {
|
|
15
|
+
value,
|
|
16
|
+
defaultValue = 0,
|
|
17
|
+
onChange,
|
|
18
|
+
min = 0,
|
|
19
|
+
max = 100,
|
|
20
|
+
step = 1,
|
|
21
|
+
disabled = false,
|
|
22
|
+
range = false,
|
|
23
|
+
vertical = false,
|
|
24
|
+
marks = {},
|
|
25
|
+
tooltip = true,
|
|
26
|
+
tooltipVisible,
|
|
27
|
+
tooltipPlacement = 'top',
|
|
28
|
+
className = '',
|
|
29
|
+
style = {},
|
|
30
|
+
size = 'md',
|
|
31
|
+
included = true,
|
|
32
|
+
dots = false,
|
|
33
|
+
reverse = false,
|
|
34
|
+
logScale = false,
|
|
35
|
+
onAfterChange,
|
|
36
|
+
onFocus,
|
|
37
|
+
onBlur,
|
|
38
|
+
startPoint,
|
|
39
|
+
trackStyle = {},
|
|
40
|
+
handleStyle = {},
|
|
41
|
+
railStyle = {},
|
|
42
|
+
dotStyle = {},
|
|
43
|
+
activeDotStyle = {},
|
|
44
|
+
id,
|
|
45
|
+
name,
|
|
46
|
+
tabIndex,
|
|
47
|
+
ariaLabel,
|
|
48
|
+
ariaLabelForHandle,
|
|
49
|
+
} = props;
|
|
50
|
+
|
|
51
|
+
const sizeConfig = SLIDER_SIZES[size] || SLIDER_SIZES.md;
|
|
52
|
+
const currentValue = value !== undefined ? value : defaultValue;
|
|
53
|
+
|
|
54
|
+
const normalizeValue = (val) => {
|
|
55
|
+
if (logScale && min > 0) {
|
|
56
|
+
return Math.log(val / min) / Math.log(max / min);
|
|
57
|
+
}
|
|
58
|
+
return (val - min) / (max - min);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const denormalizeValue = (ratio) => {
|
|
62
|
+
if (logScale && min > 0) {
|
|
63
|
+
return min * Math.pow(max / min, ratio);
|
|
64
|
+
}
|
|
65
|
+
return min + ratio * (max - min);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const getStepValue = (val) => {
|
|
69
|
+
const rounded = Math.round((val - min) / step) * step + min;
|
|
70
|
+
return Math.min(max, Math.max(min, rounded));
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const percent = (val) => {
|
|
74
|
+
const ratio = normalizeValue(val);
|
|
75
|
+
return reverse ? (1 - ratio) * 100 : ratio * 100;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const isRange = range && Array.isArray(currentValue);
|
|
79
|
+
const leftPercent = isRange ? percent(Math.min(currentValue[0], currentValue[1])) : 0;
|
|
80
|
+
const rightPercent = isRange ? percent(Math.max(currentValue[0], currentValue[1])) : percent(currentValue);
|
|
81
|
+
|
|
82
|
+
const handleMouseDown = (handleIndex, e) => {
|
|
83
|
+
if (disabled) return;
|
|
84
|
+
e.preventDefault();
|
|
85
|
+
|
|
86
|
+
const sliderEl = e.currentTarget.parentElement;
|
|
87
|
+
const rect = sliderEl.getBoundingClientRect();
|
|
88
|
+
|
|
89
|
+
const getPosition = (event) => {
|
|
90
|
+
if (vertical) {
|
|
91
|
+
const y = event.clientY - rect.top;
|
|
92
|
+
return reverse ? y / rect.height : (1 - y / rect.height);
|
|
93
|
+
}
|
|
94
|
+
const x = event.clientX - rect.left;
|
|
95
|
+
return reverse ? (1 - x / rect.width) : x / rect.width;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const handleMove = (event) => {
|
|
99
|
+
const ratio = Math.min(1, Math.max(0, getPosition(event)));
|
|
100
|
+
const rawVal = denormalizeValue(ratio);
|
|
101
|
+
const steppedVal = getStepValue(rawVal);
|
|
102
|
+
|
|
103
|
+
if (isRange) {
|
|
104
|
+
const newValues = [...currentValue];
|
|
105
|
+
newValues[handleIndex] = steppedVal;
|
|
106
|
+
onChange?.(newValues);
|
|
107
|
+
} else {
|
|
108
|
+
onChange?.(steppedVal);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const handleUp = () => {
|
|
113
|
+
document.removeEventListener('mousemove', handleMove);
|
|
114
|
+
document.removeEventListener('mouseup', handleUp);
|
|
115
|
+
onAfterChange?.(isRange ? currentValue : currentValue);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
document.addEventListener('mousemove', handleMove);
|
|
119
|
+
document.addEventListener('mouseup', handleUp);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const handleTrackClick = (e) => {
|
|
123
|
+
if (disabled) return;
|
|
124
|
+
const sliderEl = e.currentTarget;
|
|
125
|
+
const rect = sliderEl.getBoundingClientRect();
|
|
126
|
+
|
|
127
|
+
let ratio;
|
|
128
|
+
if (vertical) {
|
|
129
|
+
const y = e.clientY - rect.top;
|
|
130
|
+
ratio = reverse ? y / rect.height : (1 - y / rect.height);
|
|
131
|
+
} else {
|
|
132
|
+
const x = e.clientX - rect.left;
|
|
133
|
+
ratio = reverse ? (1 - x / rect.width) : x / rect.width;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const rawVal = denormalizeValue(ratio);
|
|
137
|
+
const steppedVal = getStepValue(rawVal);
|
|
138
|
+
|
|
139
|
+
if (isRange) {
|
|
140
|
+
const dist0 = Math.abs(currentValue[0] - steppedVal);
|
|
141
|
+
const dist1 = Math.abs(currentValue[1] - steppedVal);
|
|
142
|
+
const newValues = [...currentValue];
|
|
143
|
+
newValues[dist0 < dist1 ? 0 : 1] = steppedVal;
|
|
144
|
+
onChange?.(newValues);
|
|
145
|
+
} else {
|
|
146
|
+
onChange?.(steppedVal);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const renderTooltip = (val) => {
|
|
151
|
+
if (!tooltip || tooltipVisible === false) return null;
|
|
152
|
+
const positionClass = {
|
|
153
|
+
top: 'ed-bottom-full ed-left-1/2 ed--translate-x-1/2 ed-mb-2',
|
|
154
|
+
bottom: 'ed-top-full ed-left-1/2 ed--translate-x-1/2 ed-mt-2',
|
|
155
|
+
left: 'ed-right-full ed-top-1/2 ed--translate-y-1/2 ed-mr-2',
|
|
156
|
+
right: 'ed-left-full ed-top-1/2 ed--translate-y-1/2 ed-ml-2',
|
|
157
|
+
};
|
|
158
|
+
return ED.createElement('div', {
|
|
159
|
+
className: [
|
|
160
|
+
'ed-absolute ed-px-2 ed-py-1 ed-text-xs ed-font-medium ed-text-white ed-bg-gray-900 ed-rounded ed-whitespace-nowrap ed-pointer-events-none ed-z-10',
|
|
161
|
+
positionClass[tooltipPlacement] || positionClass.top,
|
|
162
|
+
].join(' '),
|
|
163
|
+
children: typeof val === 'number' ? Math.round(val * 100) / 100 : val,
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const markElements = Object.keys(marks).length > 0
|
|
168
|
+
? Object.entries(marks).map(([key, mark]) => {
|
|
169
|
+
const markVal = Number(key);
|
|
170
|
+
const markPercent = percent(markVal);
|
|
171
|
+
const isInRange = isRange
|
|
172
|
+
? markVal >= currentValue[0] && markVal <= currentValue[1]
|
|
173
|
+
: markVal <= currentValue;
|
|
174
|
+
|
|
175
|
+
return ED.createElement('div', {
|
|
176
|
+
key: key,
|
|
177
|
+
className: 'ed-absolute',
|
|
178
|
+
style: vertical
|
|
179
|
+
? { bottom: `${markPercent}%` }
|
|
180
|
+
: { left: `${markPercent}%` },
|
|
181
|
+
children: [
|
|
182
|
+
ED.createElement('div', {
|
|
183
|
+
key: 'dot',
|
|
184
|
+
className: [
|
|
185
|
+
'ed-w-1 ed-h-1 ed-rounded-full ed-transform ed--translate-x-1/2',
|
|
186
|
+
isInRange ? 'ed-bg-blue-500' : 'ed-bg-gray-300',
|
|
187
|
+
].join(' '),
|
|
188
|
+
}),
|
|
189
|
+
typeof mark === 'object' && mark.label
|
|
190
|
+
? ED.createElement('span', {
|
|
191
|
+
key: 'label',
|
|
192
|
+
className: `ed-transform ed--translate-x-1/2 ed-mt-2 ed-block ${sizeConfig.markFont} ed-text-gray-500`,
|
|
193
|
+
}, mark.label)
|
|
194
|
+
: typeof mark === 'string'
|
|
195
|
+
? ED.createElement('span', {
|
|
196
|
+
key: 'label',
|
|
197
|
+
className: `ed-transform ed--translate-x-1/2 ed-mt-2 ed-block ${sizeConfig.markFont} ed-text-gray-500`,
|
|
198
|
+
}, mark)
|
|
199
|
+
: null,
|
|
200
|
+
].filter(Boolean),
|
|
201
|
+
});
|
|
202
|
+
})
|
|
203
|
+
: null;
|
|
204
|
+
|
|
205
|
+
const dotElements = dots && Object.keys(marks).length === 0
|
|
206
|
+
? Array.from({ length: Math.floor((max - min) / step) + 1 }, (_, i) => {
|
|
207
|
+
const dotVal = min + i * step;
|
|
208
|
+
const dotPercent = percent(dotVal);
|
|
209
|
+
const isInRange = isRange
|
|
210
|
+
? dotVal >= currentValue[0] && dotVal <= currentValue[1]
|
|
211
|
+
: dotVal <= currentValue;
|
|
212
|
+
return ED.createElement('div', {
|
|
213
|
+
key: i,
|
|
214
|
+
className: [
|
|
215
|
+
'ed-absolute ed-w-2 ed-h-2 ed-rounded-full ed-transform ed--translate-x-1/2 ed--translate-y-1/2',
|
|
216
|
+
isInRange ? 'ed-bg-blue-500' : 'ed-bg-gray-300',
|
|
217
|
+
].join(' '),
|
|
218
|
+
style: vertical
|
|
219
|
+
? { bottom: `${dotPercent}%`, left: '50%', transform: 'translateX(-50%) translateY(50%)' }
|
|
220
|
+
: { left: `${dotPercent}%`, top: '50%', transform: 'translateX(-50%) translateY(-50%)' },
|
|
221
|
+
});
|
|
222
|
+
})
|
|
223
|
+
: null;
|
|
224
|
+
|
|
225
|
+
const renderHandle = (val, index) => {
|
|
226
|
+
const handlePercent = percent(val);
|
|
227
|
+
return ED.createElement('div', {
|
|
228
|
+
key: `handle-${index}`,
|
|
229
|
+
className: [
|
|
230
|
+
'ed-absolute ed-bg-white ed-border-2 ed-border-blue-500 ed-rounded-full',
|
|
231
|
+
'ed-shadow-md ed-cursor-grab ed-active:ed-cursor-grabbing ed-z-10',
|
|
232
|
+
'hover:ed-shadow-lg ed-transition-shadow ed-duration-150',
|
|
233
|
+
'focus:ed-outline-none focus:ed-ring-2 focus:ed-ring-blue-500 focus:ed-ring-offset-2',
|
|
234
|
+
disabled ? 'ed-cursor-not-allowed ed-opacity-50' : '',
|
|
235
|
+
].join(' '),
|
|
236
|
+
style: {
|
|
237
|
+
width: sizeConfig.handleSize,
|
|
238
|
+
height: sizeConfig.handleSize,
|
|
239
|
+
...(vertical
|
|
240
|
+
? { bottom: `calc(${handlePercent}% - ${sizeConfig.handleSize / 2}px)`, left: `calc(50% - ${sizeConfig.handleSize / 2}px)` }
|
|
241
|
+
: { left: `calc(${handlePercent}% - ${sizeConfig.handleSize / 2}px)`, top: `calc(50% - ${sizeConfig.handleSize / 2}px)` }),
|
|
242
|
+
...handleStyle,
|
|
243
|
+
},
|
|
244
|
+
onMouseDown: (e) => handleMouseDown(index, e),
|
|
245
|
+
role: 'slider',
|
|
246
|
+
'aria-valuenow': val,
|
|
247
|
+
'aria-valuemin': min,
|
|
248
|
+
'aria-valuemax': max,
|
|
249
|
+
'aria-label': ariaLabelForHandle || ariaLabel,
|
|
250
|
+
tabIndex: disabled ? -1 : (tabIndex || 0),
|
|
251
|
+
onFocus,
|
|
252
|
+
onBlur,
|
|
253
|
+
children: renderTooltip(val),
|
|
254
|
+
});
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const trackElement = included
|
|
258
|
+
? ED.createElement('div', {
|
|
259
|
+
className: 'ed-absolute ed-bg-blue-500 ed-rounded-full',
|
|
260
|
+
style: {
|
|
261
|
+
...(vertical
|
|
262
|
+
? { bottom: `${leftPercent}%`, height: `${rightPercent - leftPercent}%`, width: sizeConfig.trackHeight, left: `calc(50% - ${sizeConfig.trackHeight / 2}px)` }
|
|
263
|
+
: { left: `${leftPercent}%`, width: `${rightPercent - leftPercent}%`, height: sizeConfig.trackHeight, top: `calc(50% - ${sizeConfig.trackHeight / 2}px)` }),
|
|
264
|
+
...trackStyle,
|
|
265
|
+
},
|
|
266
|
+
})
|
|
267
|
+
: null;
|
|
268
|
+
|
|
269
|
+
const railElement = ED.createElement('div', {
|
|
270
|
+
className: 'ed-absolute ed-bg-gray-200 ed-rounded-full',
|
|
271
|
+
style: {
|
|
272
|
+
...(vertical
|
|
273
|
+
? { width: sizeConfig.trackHeight, left: `calc(50% - ${sizeConfig.trackHeight / 2}px)`, top: 0, bottom: 0 }
|
|
274
|
+
: { height: sizeConfig.trackHeight, top: `calc(50% - ${sizeConfig.trackHeight / 2}px)`, left: 0, right: 0 }),
|
|
275
|
+
...railStyle,
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
const sliderClasses = [
|
|
280
|
+
'ed-relative ed-select-none',
|
|
281
|
+
vertical ? 'ed-h-full' : 'ed-w-full',
|
|
282
|
+
disabled ? 'ed-opacity-50 ed-pointer-events-none' : '',
|
|
283
|
+
className,
|
|
284
|
+
].filter(Boolean).join(' ');
|
|
285
|
+
|
|
286
|
+
const sliderStyle = vertical
|
|
287
|
+
? { width: sizeConfig.handleSize + 10, height: '100%', ...style }
|
|
288
|
+
: { height: sizeConfig.handleSize + 10, ...style };
|
|
289
|
+
|
|
290
|
+
return ED.createElement('div', {
|
|
291
|
+
id,
|
|
292
|
+
className: sliderClasses,
|
|
293
|
+
style: sliderStyle,
|
|
294
|
+
onClick: handleTrackClick,
|
|
295
|
+
children: [
|
|
296
|
+
railElement,
|
|
297
|
+
trackElement,
|
|
298
|
+
dotElements,
|
|
299
|
+
isRange
|
|
300
|
+
? [renderHandle(currentValue[0], 0), renderHandle(currentValue[1], 1)]
|
|
301
|
+
: renderHandle(currentValue, 0),
|
|
302
|
+
markElements,
|
|
303
|
+
].flat().filter(Boolean),
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
Slider.displayName = 'Slider';
|
|
308
|
+
Slider.SIZES = SLIDER_SIZES;
|
|
309
|
+
|
|
310
|
+
module.exports = Slider;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spinner Component for ElementDrawing Framework
|
|
3
|
+
* Supports sizes, color variants, overlay, fullscreen, custom SVG, dot spinner, bar spinner
|
|
4
|
+
*/
|
|
5
|
+
const ED = require('../core/element');
|
|
6
|
+
|
|
7
|
+
const SPINNER_SIZES = {
|
|
8
|
+
xs: { main: 'ed-w-4 ed-h-4', border: 'ed-border-2', dot: 'ed-w-1 ed-h-1', bar: 'ed-w-0.5 ed-h-3', text: 'ed-text-xs' },
|
|
9
|
+
sm: { main: 'ed-w-6 ed-h-6', border: 'ed-border-2', dot: 'ed-w-1.5 ed-h-1.5', bar: 'ed-w-1 ed-h-4', text: 'ed-text-xs' },
|
|
10
|
+
md: { main: 'ed-w-8 ed-h-8', border: 'ed-border-[3px]', dot: 'ed-w-2 ed-h-2', bar: 'ed-w-1 ed-h-5', text: 'ed-text-sm' },
|
|
11
|
+
lg: { main: 'ed-w-12 ed-h-12', border: 'ed-border-4', dot: 'ed-w-2.5 ed-h-2.5', bar: 'ed-w-1.5 ed-h-7', text: 'ed-text-base' },
|
|
12
|
+
xl: { main: 'ed-w-16 ed-h-16', border: 'ed-border-4', dot: 'ed-w-3 ed-h-3', bar: 'ed-w-2 ed-h-9', text: 'ed-text-lg' },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const SPINNER_COLORS = {
|
|
16
|
+
blue: { border: 'ed-border-blue-500', text: 'ed-text-blue-500', dot: 'ed-bg-blue-500', bar: 'ed-bg-blue-500' },
|
|
17
|
+
green: { border: 'ed-border-green-500', text: 'ed-text-green-500', dot: 'ed-bg-green-500', bar: 'ed-bg-green-500' },
|
|
18
|
+
red: { border: 'ed-border-red-500', text: 'ed-text-red-500', dot: 'ed-bg-red-500', bar: 'ed-bg-red-500' },
|
|
19
|
+
yellow: { border: 'ed-border-yellow-500', text: 'ed-text-yellow-500', dot: 'ed-bg-yellow-500', bar: 'ed-bg-yellow-500' },
|
|
20
|
+
purple: { border: 'ed-border-purple-500', text: 'ed-text-purple-500', dot: 'ed-bg-purple-500', bar: 'ed-bg-purple-500' },
|
|
21
|
+
gray: { border: 'ed-border-gray-500', text: 'ed-text-gray-500', dot: 'ed-bg-gray-500', bar: 'ed-bg-gray-500' },
|
|
22
|
+
white: { border: 'ed-border-white', text: 'ed-text-white', dot: 'ed-bg-white', bar: 'ed-bg-white' },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function CircleSpinner(props) {
|
|
26
|
+
const { size = 'md', color = 'blue', sizeConfig, colorConfig } = props;
|
|
27
|
+
const sConfig = sizeConfig || SPINNER_SIZES[size] || SPINNER_SIZES.md;
|
|
28
|
+
const cConfig = colorConfig || SPINNER_COLORS[color] || SPINNER_COLORS.blue;
|
|
29
|
+
|
|
30
|
+
return ED.createElement('div', {
|
|
31
|
+
className: [
|
|
32
|
+
sConfig.main,
|
|
33
|
+
cConfig.border,
|
|
34
|
+
'ed-border-t-transparent',
|
|
35
|
+
'ed-rounded-full ed-animate-spin',
|
|
36
|
+
].join(' '),
|
|
37
|
+
role: 'status',
|
|
38
|
+
'aria-label': 'Loading',
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function DotSpinner(props) {
|
|
43
|
+
const { size = 'md', color = 'blue', sizeConfig, colorConfig } = props;
|
|
44
|
+
const sConfig = sizeConfig || SPINNER_SIZES[size] || SPINNER_SIZES.md;
|
|
45
|
+
const cConfig = colorConfig || SPINNER_COLORS[color] || SPINNER_COLORS.blue;
|
|
46
|
+
|
|
47
|
+
return ED.createElement('div', {
|
|
48
|
+
className: 'ed-flex ed-items-center ed-gap-1',
|
|
49
|
+
role: 'status',
|
|
50
|
+
'aria-label': 'Loading',
|
|
51
|
+
children: [0, 1, 2].map(i =>
|
|
52
|
+
ED.createElement('div', {
|
|
53
|
+
key: i,
|
|
54
|
+
className: [
|
|
55
|
+
sConfig.dot,
|
|
56
|
+
cConfig.dot,
|
|
57
|
+
'ed-rounded-full',
|
|
58
|
+
].join(' '),
|
|
59
|
+
style: {
|
|
60
|
+
animation: `ed-bounce 1.4s ease-in-out ${i * 0.16}s infinite both`,
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function BarSpinner(props) {
|
|
68
|
+
const { size = 'md', color = 'blue', sizeConfig, colorConfig } = props;
|
|
69
|
+
const sConfig = sizeConfig || SPINNER_SIZES[size] || SPINNER_SIZES.md;
|
|
70
|
+
const cConfig = colorConfig || SPINNER_COLORS[color] || SPINNER_COLORS.blue;
|
|
71
|
+
|
|
72
|
+
return ED.createElement('div', {
|
|
73
|
+
className: 'ed-flex ed-items-end ed-gap-0.5',
|
|
74
|
+
style: { height: sConfig.bar?.split(' ').find(s => s.startsWith('ed-h-'))?.replace('ed-h-', '') + 'px' || '20px' },
|
|
75
|
+
role: 'status',
|
|
76
|
+
'aria-label': 'Loading',
|
|
77
|
+
children: [0, 1, 2, 3].map(i =>
|
|
78
|
+
ED.createElement('div', {
|
|
79
|
+
key: i,
|
|
80
|
+
className: [
|
|
81
|
+
sConfig.bar,
|
|
82
|
+
cConfig.bar,
|
|
83
|
+
'ed-rounded-sm',
|
|
84
|
+
].join(' '),
|
|
85
|
+
style: {
|
|
86
|
+
animation: `ed-bar-stretch 1.2s ease-in-out ${i * 0.1}s infinite`,
|
|
87
|
+
},
|
|
88
|
+
})
|
|
89
|
+
),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function Spinner(props) {
|
|
94
|
+
const {
|
|
95
|
+
variant = 'circle',
|
|
96
|
+
size = 'md',
|
|
97
|
+
color = 'blue',
|
|
98
|
+
overlay = false,
|
|
99
|
+
fullscreen = false,
|
|
100
|
+
className = '',
|
|
101
|
+
style = {},
|
|
102
|
+
label,
|
|
103
|
+
labelPosition = 'bottom',
|
|
104
|
+
customSvg,
|
|
105
|
+
spinning = true,
|
|
106
|
+
children,
|
|
107
|
+
delay = 0,
|
|
108
|
+
tip,
|
|
109
|
+
} = props;
|
|
110
|
+
|
|
111
|
+
if (!spinning) return children || null;
|
|
112
|
+
|
|
113
|
+
const sizeConfig = SPINNER_SIZES[size] || SPINNER_SIZES.md;
|
|
114
|
+
const colorConfig = SPINNER_COLORS[color] || SPINNER_COLORS.blue;
|
|
115
|
+
|
|
116
|
+
const renderSpinner = () => {
|
|
117
|
+
if (customSvg) {
|
|
118
|
+
return ED.createElement('div', {
|
|
119
|
+
className: `ed-animate-spin ${sizeConfig.main}`,
|
|
120
|
+
role: 'status',
|
|
121
|
+
'aria-label': 'Loading',
|
|
122
|
+
children: customSvg,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
switch (variant) {
|
|
127
|
+
case 'dot':
|
|
128
|
+
return ED.createElement(DotSpinner, { size, color, sizeConfig, colorConfig });
|
|
129
|
+
case 'bar':
|
|
130
|
+
return ED.createElement(BarSpinner, { size, color, sizeConfig, colorConfig });
|
|
131
|
+
case 'circle':
|
|
132
|
+
default:
|
|
133
|
+
return ED.createElement(CircleSpinner, { size, color, sizeConfig, colorConfig });
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const labelElement = label || tip
|
|
138
|
+
? ED.createElement('span', {
|
|
139
|
+
className: `${sizeConfig.text} ${colorConfig.text} ed-font-medium`,
|
|
140
|
+
}, label || tip)
|
|
141
|
+
: null;
|
|
142
|
+
|
|
143
|
+
const spinnerContent = ED.createElement('div', {
|
|
144
|
+
className: [
|
|
145
|
+
'ed-inline-flex ed-items-center ed-gap-2',
|
|
146
|
+
labelPosition === 'bottom' ? 'ed-flex-col' : labelPosition === 'right' ? 'ed-flex-row' : 'ed-flex-col',
|
|
147
|
+
className,
|
|
148
|
+
].filter(Boolean).join(' '),
|
|
149
|
+
style,
|
|
150
|
+
children: [
|
|
151
|
+
labelPosition === 'bottom' ? renderSpinner() : labelElement,
|
|
152
|
+
labelPosition === 'bottom' ? labelElement : renderSpinner(),
|
|
153
|
+
],
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
if (fullscreen) {
|
|
157
|
+
return ED.createElement('div', {
|
|
158
|
+
className: 'ed-fixed ed-inset-0 ed-bg-white ed-bg-opacity-80 ed-flex ed-items-center ed-justify-center ed-z-[9999]',
|
|
159
|
+
children: spinnerContent,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (overlay) {
|
|
164
|
+
return ED.createElement('div', {
|
|
165
|
+
className: 'ed-relative',
|
|
166
|
+
children: [
|
|
167
|
+
children,
|
|
168
|
+
ED.createElement('div', {
|
|
169
|
+
key: 'overlay',
|
|
170
|
+
className: 'ed-absolute ed-inset-0 ed-bg-white ed-bg-opacity-70 ed-flex ed-items-center ed-justify-center ed-rounded-inherit ed-z-10',
|
|
171
|
+
children: spinnerContent,
|
|
172
|
+
}),
|
|
173
|
+
],
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (children) {
|
|
178
|
+
return ED.createElement('div', { className: 'ed-relative' }, [
|
|
179
|
+
children,
|
|
180
|
+
ED.createElement('div', {
|
|
181
|
+
key: 'spinner-overlay',
|
|
182
|
+
className: 'ed-absolute ed-inset-0 ed-flex ed-items-center ed-justify-center ed-bg-white ed-bg-opacity-60 ed-z-10',
|
|
183
|
+
children: renderSpinner(),
|
|
184
|
+
}),
|
|
185
|
+
]);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return spinnerContent;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
Spinner.displayName = 'Spinner';
|
|
192
|
+
Spinner.Circle = CircleSpinner;
|
|
193
|
+
Spinner.Dot = DotSpinner;
|
|
194
|
+
Spinner.Bar = BarSpinner;
|
|
195
|
+
Spinner.SIZES = SPINNER_SIZES;
|
|
196
|
+
Spinner.COLORS = SPINNER_COLORS;
|
|
197
|
+
|
|
198
|
+
module.exports = Spinner;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Switch Component for ElementDrawing Framework
|
|
3
|
+
* Supports sizes, colors, disabled, loading, icons, labels, custom width/height
|
|
4
|
+
*/
|
|
5
|
+
const ED = require('../core/element');
|
|
6
|
+
|
|
7
|
+
const SWITCH_SIZES = {
|
|
8
|
+
sm: { width: 36, height: 20, handleSize: 14, text: 'ed-text-[10px]', translate: 16 },
|
|
9
|
+
md: { width: 44, height: 24, handleSize: 18, text: 'ed-text-xs', translate: 20 },
|
|
10
|
+
lg: { width: 56, height: 28, handleSize: 22, text: 'ed-text-sm', translate: 28 },
|
|
11
|
+
xl: { width: 64, height: 32, handleSize: 26, text: 'ed-text-sm', translate: 32 },
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const SWITCH_COLORS = {
|
|
15
|
+
blue: 'ed-bg-blue-600',
|
|
16
|
+
green: 'ed-bg-green-600',
|
|
17
|
+
red: 'ed-bg-red-600',
|
|
18
|
+
yellow: 'ed-bg-yellow-500',
|
|
19
|
+
purple: 'ed-bg-purple-600',
|
|
20
|
+
cyan: 'ed-bg-cyan-600',
|
|
21
|
+
orange: 'ed-bg-orange-500',
|
|
22
|
+
pink: 'ed-bg-pink-600',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function Switch(props) {
|
|
26
|
+
const {
|
|
27
|
+
checked,
|
|
28
|
+
defaultChecked = false,
|
|
29
|
+
onChange,
|
|
30
|
+
disabled = false,
|
|
31
|
+
loading = false,
|
|
32
|
+
size = 'md',
|
|
33
|
+
color = 'blue',
|
|
34
|
+
checkedIcon,
|
|
35
|
+
uncheckedIcon,
|
|
36
|
+
checkedChildren,
|
|
37
|
+
uncheckedChildren,
|
|
38
|
+
className = '',
|
|
39
|
+
style = {},
|
|
40
|
+
width: customWidth,
|
|
41
|
+
height: customHeight,
|
|
42
|
+
id,
|
|
43
|
+
name,
|
|
44
|
+
value,
|
|
45
|
+
autoFocus = false,
|
|
46
|
+
tabIndex,
|
|
47
|
+
onFocus,
|
|
48
|
+
onBlur,
|
|
49
|
+
ariaLabel,
|
|
50
|
+
beforeChange,
|
|
51
|
+
label,
|
|
52
|
+
labelPosition = 'right',
|
|
53
|
+
} = props;
|
|
54
|
+
|
|
55
|
+
const isChecked = checked !== undefined ? checked : defaultChecked;
|
|
56
|
+
const sizeConfig = SWITCH_SIZES[size] || SWITCH_SIZES.md;
|
|
57
|
+
const colorClass = SWITCH_COLORS[color] || SWITCH_COLORS.blue;
|
|
58
|
+
|
|
59
|
+
const switchWidth = customWidth || sizeConfig.width;
|
|
60
|
+
const switchHeight = customHeight || sizeConfig.height;
|
|
61
|
+
const handleSize = sizeConfig.handleSize;
|
|
62
|
+
const translateX = isChecked ? (switchWidth - handleSize - 6) : 0;
|
|
63
|
+
|
|
64
|
+
const trackClasses = [
|
|
65
|
+
'ed-relative ed-inline-flex ed-items-center ed-rounded-full ed-transition-colors ed-duration-200 ed-cursor-pointer',
|
|
66
|
+
isChecked ? colorClass : 'ed-bg-gray-300',
|
|
67
|
+
disabled || loading ? 'ed-opacity-50 ed-cursor-not-allowed' : '',
|
|
68
|
+
className,
|
|
69
|
+
].filter(Boolean).join(' ');
|
|
70
|
+
|
|
71
|
+
const handleClasses = [
|
|
72
|
+
'ed-absolute ed-bg-white ed-rounded-full ed-shadow-sm ed-transition-transform ed-duration-200 ed-flex ed-items-center ed-justify-center',
|
|
73
|
+
loading ? '' : '',
|
|
74
|
+
].join(' ');
|
|
75
|
+
|
|
76
|
+
const loadingSpinner = loading
|
|
77
|
+
? ED.createElement('svg', {
|
|
78
|
+
className: `ed-animate-spin ed-text-${isChecked ? color : 'gray'}-500`,
|
|
79
|
+
style: { width: handleSize * 0.5, height: handleSize * 0.5 },
|
|
80
|
+
fill: 'none',
|
|
81
|
+
viewBox: '0 0 24 24',
|
|
82
|
+
children: [
|
|
83
|
+
ED.createElement('circle', {
|
|
84
|
+
key: 'bg',
|
|
85
|
+
className: 'ed-opacity-25',
|
|
86
|
+
cx: '12', cy: '12', r: '10',
|
|
87
|
+
stroke: 'currentColor', strokeWidth: '4',
|
|
88
|
+
}),
|
|
89
|
+
ED.createElement('path', {
|
|
90
|
+
key: 'fg',
|
|
91
|
+
className: 'ed-opacity-75',
|
|
92
|
+
fill: 'currentColor',
|
|
93
|
+
d: 'M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z',
|
|
94
|
+
}),
|
|
95
|
+
],
|
|
96
|
+
})
|
|
97
|
+
: null;
|
|
98
|
+
|
|
99
|
+
const checkedIconElement = checkedIcon && isChecked && !loading
|
|
100
|
+
? ED.createElement('span', {
|
|
101
|
+
className: 'ed-text-white',
|
|
102
|
+
style: { width: handleSize * 0.5, height: handleSize * 0.5 },
|
|
103
|
+
children: typeof checkedIcon === 'string' ? ED.createElement('i', { className: checkedIcon }) : checkedIcon,
|
|
104
|
+
})
|
|
105
|
+
: null;
|
|
106
|
+
|
|
107
|
+
const uncheckedIconElement = uncheckedIcon && !isChecked && !loading
|
|
108
|
+
? ED.createElement('span', {
|
|
109
|
+
className: 'ed-text-gray-500',
|
|
110
|
+
style: { width: handleSize * 0.5, height: handleSize * 0.5 },
|
|
111
|
+
children: typeof uncheckedIcon === 'string' ? ED.createElement('i', { className: uncheckedIcon }) : uncheckedIcon,
|
|
112
|
+
})
|
|
113
|
+
: null;
|
|
114
|
+
|
|
115
|
+
const checkedText = checkedChildren && isChecked
|
|
116
|
+
? ED.createElement('span', {
|
|
117
|
+
className: `${sizeConfig.text} ed-text-white ed-font-medium ed-pl-2 ed-pr-${handleSize / 4 + 2} ed-select-none`,
|
|
118
|
+
style: { paddingRight: handleSize + 4 },
|
|
119
|
+
}, checkedChildren)
|
|
120
|
+
: null;
|
|
121
|
+
|
|
122
|
+
const uncheckedText = uncheckedChildren && !isChecked
|
|
123
|
+
? ED.createElement('span', {
|
|
124
|
+
className: `${sizeConfig.text} ed-text-gray-500 ed-font-medium ed-pr-2 ed-pl-${handleSize / 4 + 2} ed-select-none`,
|
|
125
|
+
style: { paddingLeft: handleSize + 4 },
|
|
126
|
+
}, uncheckedChildren)
|
|
127
|
+
: null;
|
|
128
|
+
|
|
129
|
+
const handleClick = () => {
|
|
130
|
+
if (disabled || loading) return;
|
|
131
|
+
if (beforeChange) {
|
|
132
|
+
const result = beforeChange(!isChecked);
|
|
133
|
+
if (result === false) return;
|
|
134
|
+
}
|
|
135
|
+
onChange?.(!isChecked);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const handleKeyDown = (e) => {
|
|
139
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
140
|
+
e.preventDefault();
|
|
141
|
+
handleClick();
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const switchElement = ED.createElement('button', {
|
|
146
|
+
id,
|
|
147
|
+
className: trackClasses,
|
|
148
|
+
style: {
|
|
149
|
+
width: switchWidth,
|
|
150
|
+
height: switchHeight,
|
|
151
|
+
minWidth: switchWidth,
|
|
152
|
+
...style,
|
|
153
|
+
},
|
|
154
|
+
onClick: handleClick,
|
|
155
|
+
onKeyDown: handleKeyDown,
|
|
156
|
+
role: 'switch',
|
|
157
|
+
'aria-checked': isChecked,
|
|
158
|
+
'aria-label': ariaLabel || label,
|
|
159
|
+
tabIndex: disabled ? -1 : (tabIndex || 0),
|
|
160
|
+
autoFocus,
|
|
161
|
+
onFocus,
|
|
162
|
+
onBlur,
|
|
163
|
+
disabled: disabled || loading,
|
|
164
|
+
children: [
|
|
165
|
+
uncheckedText,
|
|
166
|
+
checkedText,
|
|
167
|
+
ED.createElement('span', {
|
|
168
|
+
key: 'handle',
|
|
169
|
+
className: handleClasses,
|
|
170
|
+
style: {
|
|
171
|
+
width: handleSize,
|
|
172
|
+
height: handleSize,
|
|
173
|
+
left: 3,
|
|
174
|
+
transform: `translateX(${translateX}px)`,
|
|
175
|
+
},
|
|
176
|
+
children: [loadingSpinner, checkedIconElement, uncheckedIconElement].filter(Boolean)[0] || null,
|
|
177
|
+
}),
|
|
178
|
+
],
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const labelElement = label
|
|
182
|
+
? ED.createElement('span', {
|
|
183
|
+
className: `ed-text-sm ed-font-medium ${disabled ? 'ed-text-gray-400' : 'ed-text-gray-700'}`,
|
|
184
|
+
}, label)
|
|
185
|
+
: null;
|
|
186
|
+
|
|
187
|
+
if (label) {
|
|
188
|
+
return ED.createElement('div', {
|
|
189
|
+
className: `ed-inline-flex ed-items-center ed-gap-2 ${labelPosition === 'left' ? 'ed-flex-row-reverse' : ''}`,
|
|
190
|
+
children: [switchElement, labelElement],
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return switchElement;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
Switch.displayName = 'Switch';
|
|
198
|
+
Switch.SIZES = SWITCH_SIZES;
|
|
199
|
+
Switch.COLORS = SWITCH_COLORS;
|
|
200
|
+
|
|
201
|
+
module.exports = Switch;
|