flet-box 0.0.0-stage → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +190 -2
  3. package/bin/cli.js +394 -0
  4. package/bin/commands/buildAndroid.js +200 -0
  5. package/bin/commands/buildIOS.js +237 -0
  6. package/bin/commands/create.js +406 -0
  7. package/bin/commands/createBundle.js +375 -0
  8. package/bin/commands/createComponent.js +115 -0
  9. package/bin/commands/createScreen.js +161 -0
  10. package/bin/commands/killServer.js +107 -0
  11. package/bin/commands/package.js +131 -0
  12. package/bin/commands/release.js +189 -0
  13. package/bin/commands/runBundle.js +62 -0
  14. package/bin/commands/runCloudflare.js +89 -0
  15. package/bin/commands/runNative.js +109 -0
  16. package/bin/commands/runServer.js +390 -0
  17. package/bin/ota/loader.js +361 -0
  18. package/bin/utils/bundleConfig.js +178 -0
  19. package/bin/utils/capacitor.js +137 -0
  20. package/bin/utils/colors.js +215 -0
  21. package/bin/utils/helpers.js +80 -0
  22. package/bin/utils/spinner.js +96 -0
  23. package/bin/utils/templates.js +1824 -0
  24. package/bin/utils/version.js +108 -0
  25. package/package.json +58 -4
  26. package/src/animations/AnimatedBox.js +607 -0
  27. package/src/animations/AnimatedText.js +105 -0
  28. package/src/animations/MatrixRain.js +220 -0
  29. package/src/animations/ParallaxBox.js +369 -0
  30. package/src/animations/animate.js +332 -0
  31. package/src/animations/index.js +25 -0
  32. package/src/app.js +41 -0
  33. package/src/assets/icon-1024-maskable.png +0 -0
  34. package/src/assets/icon-192-maskable.png +0 -0
  35. package/src/assets/icon-192.png +0 -0
  36. package/src/assets/icon-512-maskable.png +0 -0
  37. package/src/assets/icon-512.png +0 -0
  38. package/src/assets/logo.png +0 -0
  39. package/src/core/App.js +44 -0
  40. package/src/core/hmr-client.js +142 -0
  41. package/src/core/index.js +34 -0
  42. package/src/core/pwa.js +100 -0
  43. package/src/core/runApp.js +430 -0
  44. package/src/create-index.sh +237 -0
  45. package/src/fonts/MaterialIcons-Regular.woff2 +0 -0
  46. package/src/fonts/icons.css +23 -0
  47. package/src/index.d.ts +1930 -0
  48. package/src/index.js +288 -0
  49. package/src/navigations/AdaptiveScaffold.js +149 -0
  50. package/src/navigations/AppBar.js +331 -0
  51. package/src/navigations/BottomNavigation.js +298 -0
  52. package/src/navigations/CollapsibleSideBar.js +139 -0
  53. package/src/navigations/Drawer.js +333 -0
  54. package/src/navigations/DrawerItem.js +260 -0
  55. package/src/navigations/Router.js +475 -0
  56. package/src/navigations/Scaffold.js +537 -0
  57. package/src/navigations/Tabs.js +384 -0
  58. package/src/navigations/index.js +57 -0
  59. package/src/services/RamStore.js +213 -0
  60. package/src/services/Session.js +310 -0
  61. package/src/services/Storage.js +309 -0
  62. package/src/services/http.js +350 -0
  63. package/src/services/index.js +57 -0
  64. package/src/tools/animation.js +42 -0
  65. package/src/tools/array.js +95 -0
  66. package/src/tools/border.js +28 -0
  67. package/src/tools/clipboard.js +104 -0
  68. package/src/tools/color.js +329 -0
  69. package/src/tools/createList.js +215 -0
  70. package/src/tools/delay.js +55 -0
  71. package/src/tools/device.js +138 -0
  72. package/src/tools/dict.js +297 -0
  73. package/src/tools/dimensions.js +118 -0
  74. package/src/tools/filter.js +41 -0
  75. package/src/tools/flex.js +26 -0
  76. package/src/tools/gradient.js +34 -0
  77. package/src/tools/grid.js +57 -0
  78. package/src/tools/index.js +38 -0
  79. package/src/tools/mapList.js +107 -0
  80. package/src/tools/margin.js +93 -0
  81. package/src/tools/memo.js +60 -0
  82. package/src/tools/os.js +104 -0
  83. package/src/tools/padding.js +93 -0
  84. package/src/tools/print.js +69 -0
  85. package/src/tools/random.js +299 -0
  86. package/src/tools/ref.js +70 -0
  87. package/src/tools/rgba.js +4 -0
  88. package/src/tools/shadow.js +23 -0
  89. package/src/tools/string.js +85 -0
  90. package/src/tools/time.js +674 -0
  91. package/src/tools/transform.js +68 -0
  92. package/src/tools/transition.js +48 -0
  93. package/src/tools/useState.js +268 -0
  94. package/src/tools/uuid.js +42 -0
  95. package/src/utils/TextInputValidator.js +256 -0
  96. package/src/utils/composeUpdate.js +65 -0
  97. package/src/utils/getWidgetProps.js +35 -0
  98. package/src/utils/index.js +76 -0
  99. package/src/utils/markdownParser.js +368 -0
  100. package/src/utils/mediaTime.js +79 -0
  101. package/src/utils/navigation.js +263 -0
  102. package/src/utils/scriptInjector.js +73 -0
  103. package/src/utils/stackPosition.js +31 -0
  104. package/src/utils/stopWebRefresh.js +77 -0
  105. package/src/utils/styleInjector.js +38 -0
  106. package/src/utils/syntaxHighlight.js +339 -0
  107. package/src/utils/themes.js +347 -0
  108. package/src/utils/units.js +87 -0
  109. package/src/utils/visualEffects.js +169 -0
  110. package/src/widget-factory/addChildren.js +114 -0
  111. package/src/widget-factory/assignProps.js +256 -0
  112. package/src/widget-factory/createWidget.js +28 -0
  113. package/src/widget-factory/effects.js +132 -0
  114. package/src/widget-factory/index.js +7 -0
  115. package/src/widget-factory/lifecycle.js +210 -0
  116. package/src/widget-factory/makeParentable.js +89 -0
  117. package/src/widget-factory/pixTool.js +40 -0
  118. package/src/widget-factory/processProps.js +122 -0
  119. package/src/widget-factory/reactivity.js +98 -0
  120. package/src/widget-factory/remTool.js +34 -0
  121. package/src/widget-factory/stackPosition.js +87 -0
  122. package/src/widget-factory/stringTool.js +26 -0
  123. package/src/widget-factory/tools.js +249 -0
  124. package/src/widget-factory/translateProps.js +1049 -0
  125. package/src/widget-factory/warnings.js +284 -0
  126. package/src/widget-factory/widgetFactory.js +200 -0
  127. package/src/widgets/Accordion.js +562 -0
  128. package/src/widgets/AdMob.js +315 -0
  129. package/src/widgets/AdSense.js +268 -0
  130. package/src/widgets/AlertDialog.js +162 -0
  131. package/src/widgets/Audio.js +311 -0
  132. package/src/widgets/Avatar.js +210 -0
  133. package/src/widgets/Badge.js +189 -0
  134. package/src/widgets/BottomSheet.js +287 -0
  135. package/src/widgets/Button.js +247 -0
  136. package/src/widgets/Card.js +33 -0
  137. package/src/widgets/Carousel.js +301 -0
  138. package/src/widgets/Chart.js +632 -0
  139. package/src/widgets/Checkbox.js +106 -0
  140. package/src/widgets/Chip.js +153 -0
  141. package/src/widgets/CircularBar.js +352 -0
  142. package/src/widgets/CircularChart.js +549 -0
  143. package/src/widgets/CodeViewer.js +236 -0
  144. package/src/widgets/Column.js +29 -0
  145. package/src/widgets/Container.js +31 -0
  146. package/src/widgets/DataTable.js +257 -0
  147. package/src/widgets/Divider.js +93 -0
  148. package/src/widgets/DraggBox.js +115 -0
  149. package/src/widgets/Dropdown.js +505 -0
  150. package/src/widgets/DroppBox.js +180 -0
  151. package/src/widgets/FloatingActionButton.js +91 -0
  152. package/src/widgets/GridView.js +20 -0
  153. package/src/widgets/Icon.js +33 -0
  154. package/src/widgets/Image.js +15 -0
  155. package/src/widgets/Input.js +565 -0
  156. package/src/widgets/Inspector.js +254 -0
  157. package/src/widgets/InstallButton.js +110 -0
  158. package/src/widgets/ListTile.js +224 -0
  159. package/src/widgets/ListView.js +503 -0
  160. package/src/widgets/Markdown.js +1236 -0
  161. package/src/widgets/Modal.js +496 -0
  162. package/src/widgets/Pagination.js +195 -0
  163. package/src/widgets/ProgressBar.js +178 -0
  164. package/src/widgets/QRCode.js +467 -0
  165. package/src/widgets/Radio.js +132 -0
  166. package/src/widgets/Rating.js +261 -0
  167. package/src/widgets/Row.js +29 -0
  168. package/src/widgets/Skeleton.js +243 -0
  169. package/src/widgets/Slider.js +527 -0
  170. package/src/widgets/SnackBar.js +227 -0
  171. package/src/widgets/Stack.js +29 -0
  172. package/src/widgets/Stepper.js +296 -0
  173. package/src/widgets/Switch.js +102 -0
  174. package/src/widgets/Text.js +98 -0
  175. package/src/widgets/Tooltip.js +267 -0
  176. package/src/widgets/TreeView.js +370 -0
  177. package/src/widgets/Video.js +92 -0
  178. package/src/widgets/index.js +52 -0
@@ -0,0 +1,132 @@
1
+ /**
2
+ * @file Radio.js
3
+ * @description Radio input with controlled/uncontrolled selection state.
4
+ */
5
+ import { WidgetFactory } from "../widget-factory/index.js";
6
+ import { colors } from "../utils/themes.js";
7
+
8
+ export const Radio = (props) => {
9
+ const {
10
+ selected = false,
11
+ onSelect,
12
+ onPress: onPressProp,
13
+ disabled = false,
14
+ size = 20,
15
+ name,
16
+ ...rest
17
+ } = props;
18
+
19
+ /** @deprecated Use onPress instead */
20
+ // onSelect kept as alias for backward compatibility
21
+ const onPress = onPressProp || onSelect;
22
+
23
+ let internalSelected = selected;
24
+ let radioRef = null;
25
+ let dotRef = null;
26
+
27
+ const updateUI = (newSelected) => {
28
+ if (radioRef) {
29
+ radioRef.style.backgroundColor = newSelected
30
+ ? colors.primary
31
+ : "transparent";
32
+ radioRef.style.border = `2px solid ${newSelected ? colors.primary : colors.border}`;
33
+ }
34
+ if (dotRef) {
35
+ dotRef.style.display = newSelected ? "block" : "none";
36
+ }
37
+ };
38
+
39
+ // Createste dot
40
+ const dot = WidgetFactory({
41
+ width: size / 2,
42
+ height: size / 2,
43
+ backgroundColor: "#fff",
44
+ borderRadius: "50%",
45
+ display: internalSelected ? "block" : "none",
46
+ });
47
+ dotRef = dot;
48
+
49
+ // Createste main radio container
50
+ const radio = WidgetFactory({
51
+ widgetName: "Radio",
52
+ width: size,
53
+ height: size,
54
+ border: `2px solid ${disabled ? colors.border : internalSelected ? colors.primary : colors.border}`,
55
+ borderRadius: "50%",
56
+ backgroundColor: internalSelected ? colors.primary : "transparent",
57
+ display: "flex",
58
+ alignItems: "center",
59
+ justifyContent: "center",
60
+ transition: "all 0.2s ease",
61
+ cursor: disabled ? "not-allowed" : "pointer",
62
+ opacity: disabled ? 0.5 : 1,
63
+ child: dot,
64
+ ...rest,
65
+ });
66
+ radioRef = radio;
67
+
68
+ // Handle click
69
+ if (!disabled) {
70
+ radio.onclick = (e) => {
71
+ e.stopPropagation();
72
+ if (internalSelected) return;
73
+
74
+ const newSelected = true;
75
+ internalSelected = newSelected;
76
+ updateUI(newSelected);
77
+
78
+ if (name) {
79
+ const allRadios = document.querySelectorAll(
80
+ `[data-radio-group="${name}"]`,
81
+ );
82
+ allRadios.forEach((otherRadio) => {
83
+ if (otherRadio !== radio && otherRadio.unselect) {
84
+ otherRadio.unselect();
85
+ }
86
+ });
87
+ }
88
+
89
+ if (onPress) onPress(true);
90
+ };
91
+
92
+ // Hover effect
93
+ radio.addEventListener("mouseenter", () => {
94
+ if (!disabled && !internalSelected) {
95
+ radio.style.backgroundColor = colors.primary + "20";
96
+ }
97
+ });
98
+
99
+ radio.addEventListener("mouseleave", () => {
100
+ if (!disabled && !internalSelected) {
101
+ radio.style.backgroundColor = "transparent";
102
+ }
103
+ });
104
+ }
105
+
106
+ // Public methods
107
+ radio.select = () => {
108
+ if (!disabled && !internalSelected) {
109
+ internalSelected = true;
110
+ updateUI(true);
111
+ if (onPress) onPress(true);
112
+ }
113
+ };
114
+
115
+ radio.unselect = () => {
116
+ if (!disabled && internalSelected) {
117
+ internalSelected = false;
118
+ updateUI(false);
119
+ if (onPress) onPress(false);
120
+ }
121
+ };
122
+
123
+ radio.isSelected = () => internalSelected;
124
+
125
+ if (name) {
126
+ radio.setAttribute("data-radio-group", name);
127
+ }
128
+
129
+ return radio;
130
+ };
131
+
132
+ export default Radio;
@@ -0,0 +1,261 @@
1
+ /**
2
+ * @file Rating.js
3
+ * @description Star rating supporting mouse, touch and swipe interactions.
4
+ */
5
+ import { WidgetFactory } from "../widget-factory/index.js";
6
+ import { Row } from "./Row.js";
7
+ import { Text } from "./Text.js";
8
+ import { Icon } from "./Icon.js";
9
+ import { colors, subscribeTheme } from "../utils/themes.js";
10
+
11
+ export const Rating = (props) => {
12
+ const {
13
+ value = 0,
14
+ max = 5,
15
+ onChange,
16
+ onPress,
17
+ readOnly = false,
18
+ size = 20,
19
+ activeColor: propActiveColor,
20
+ inactiveColor: propInactiveColor,
21
+ iconActive = "star",
22
+ iconInactive = "star_border",
23
+ iconHalf = "star_half",
24
+ gap = 2,
25
+ allowHalf = false,
26
+ showValue = false,
27
+ valueColor = colors.textSecondary,
28
+ valueSize,
29
+ ...rest
30
+ } = props;
31
+
32
+ // Colors from theme
33
+ let activeColor =
34
+ propActiveColor !== undefined ? propActiveColor : colors.warning;
35
+ let inactiveColor =
36
+ propInactiveColor !== undefined ? propInactiveColor : colors.border;
37
+ let currentValue = Math.min(Math.max(value, 0), max);
38
+
39
+ let starIcons = []; // stores the SPAN elements of each star
40
+ let starWrappers = []; // stores the wrappers (for events)
41
+ let valueTextRef = null;
42
+ let unsubscribeTheme = null;
43
+ let touchActive = false; // to avoid conflicts with mouse
44
+
45
+ // Update all stars according to currentValue
46
+ const updateStars = () => {
47
+ for (let i = 0; i < max; i++) {
48
+ const starNumber = i + 1;
49
+ const span = starIcons[i];
50
+ if (!span) continue;
51
+
52
+ let iconName = iconInactive;
53
+ let color = inactiveColor;
54
+
55
+ if (starNumber <= currentValue) {
56
+ iconName = iconActive;
57
+ color = activeColor;
58
+ } else if (allowHalf && starNumber - 0.5 === currentValue) {
59
+ iconName = iconHalf;
60
+ color = activeColor;
61
+ }
62
+
63
+ span.textContent = iconName;
64
+ span.style.color = color;
65
+ }
66
+ if (valueTextRef && showValue) {
67
+ valueTextRef.update({ text: currentValue.toFixed(1) });
68
+ }
69
+ };
70
+
71
+ // Change the actual value (persistent)
72
+ const setValue = (newValue) => {
73
+ if (readOnly) return;
74
+ let clamped = Math.min(Math.max(newValue, 0), max);
75
+ if (clamped === currentValue) return;
76
+ currentValue = clamped;
77
+ updateStars();
78
+ const cb = onChange || onPress;
79
+ if (cb) cb(currentValue);
80
+ };
81
+
82
+ // Temporary preview (hover or touchmove)
83
+ const previewValue = (starIndex) => {
84
+ for (let i = 0; i < max; i++) {
85
+ const span = starIcons[i];
86
+ if (!span) continue;
87
+ if (i <= starIndex) {
88
+ span.textContent = iconActive;
89
+ span.style.color = activeColor;
90
+ } else {
91
+ span.textContent = iconInactive;
92
+ span.style.color = inactiveColor;
93
+ }
94
+ }
95
+ };
96
+
97
+ // Restore the display according to the actual value
98
+ const restoreFromPreview = () => {
99
+ updateStars();
100
+ };
101
+
102
+ // ========== STAR CREATION ==========
103
+ for (let i = 0; i < max; i++) {
104
+ const icon = Icon({
105
+ name: iconInactive,
106
+ size: size,
107
+ color: inactiveColor,
108
+ });
109
+ const wrapper = WidgetFactory({
110
+ display: "inline-flex",
111
+ cursor: readOnly ? "default" : "pointer",
112
+ transition: "transform 0.1s ease",
113
+ child: icon,
114
+ padding: "4px", // larger touch area
115
+ });
116
+
117
+ starIcons.push(icon);
118
+ starWrappers.push(wrapper);
119
+
120
+ if (!readOnly) {
121
+ // ----- MOUSE EVENTS -----
122
+ wrapper.addEventListener("mouseenter", () => {
123
+ if (touchActive) return; // avoid conflict with touch
124
+ wrapper.style.transform = "scale(1.15)";
125
+ previewValue(i);
126
+ });
127
+ wrapper.addEventListener("mouseleave", () => {
128
+ if (touchActive) return;
129
+ wrapper.style.transform = "scale(1)";
130
+ restoreFromPreview();
131
+ });
132
+ wrapper.addEventListener("click", () => {
133
+ if (touchActive) return;
134
+ let newValue = i + 1;
135
+ if (allowHalf) {
136
+ if (currentValue === newValue) newValue = newValue - 0.5;
137
+ else if (currentValue === newValue - 0.5) newValue = newValue;
138
+ else newValue = newValue;
139
+ }
140
+ setValue(newValue);
141
+ });
142
+
143
+ // ----- TOUCH EVENTS -----
144
+ wrapper.addEventListener("touchstart", (e) => {
145
+ e.preventDefault();
146
+ touchActive = true;
147
+ wrapper.style.transform = "scale(1.15)";
148
+ previewValue(i);
149
+ });
150
+ wrapper.addEventListener("touchmove", (e) => {
151
+ e.preventDefault();
152
+ const touch = e.touches[0];
153
+ const elemUnderTouch = document.elementFromPoint(
154
+ touch.clientX,
155
+ touch.clientY,
156
+ );
157
+ let targetIndex = -1;
158
+ for (let idx = 0; idx < starWrappers.length; idx++) {
159
+ if (starWrappers[idx].contains(elemUnderTouch)) {
160
+ targetIndex = idx;
161
+ break;
162
+ }
163
+ }
164
+ if (targetIndex !== -1 && targetIndex !== i) {
165
+ previewValue(targetIndex);
166
+ } else if (targetIndex === -1) {
167
+ restoreFromPreview();
168
+ }
169
+ });
170
+ wrapper.addEventListener("touchend", (e) => {
171
+ e.preventDefault();
172
+ wrapper.style.transform = "scale(1)";
173
+ // Determine which star is currently previewed (the one with iconActive)
174
+ let selectedIndex = -1;
175
+ for (let idx = 0; idx < starIcons.length; idx++) {
176
+ if (starIcons[idx].textContent === iconActive) {
177
+ selectedIndex = idx;
178
+ break;
179
+ }
180
+ }
181
+ if (selectedIndex !== -1) {
182
+ let newValue = selectedIndex + 1;
183
+ if (allowHalf) {
184
+ // For touch, we always consider the integer; if you want half a star, it can be adjusted
185
+ }
186
+ setValue(newValue);
187
+ } else {
188
+ restoreFromPreview();
189
+ }
190
+ touchActive = false;
191
+ });
192
+ wrapper.addEventListener("touchcancel", (e) => {
193
+ wrapper.style.transform = "scale(1)";
194
+ restoreFromPreview();
195
+ touchActive = false;
196
+ });
197
+ }
198
+ }
199
+
200
+ // Stars container
201
+ const starsContainer = Row({
202
+ alignItems: "center",
203
+ gap: gap,
204
+ flexWrap: "wrap",
205
+ children: starWrappers,
206
+ });
207
+
208
+ // Ensure initial state
209
+ updateStars();
210
+
211
+ // Final construction
212
+ const children = [starsContainer];
213
+ if (showValue) {
214
+ const valueText = Text({
215
+ text: currentValue.toFixed(1),
216
+ size: valueSize || size * 0.7,
217
+ color: valueColor,
218
+ weight: "bold",
219
+ minWidth: "40px",
220
+ textAlign: "center",
221
+ });
222
+ valueTextRef = valueText;
223
+ children.push(valueText);
224
+ }
225
+
226
+ const ratingContainer = WidgetFactory({
227
+ widgetName: "Rating",
228
+ display: "inline-flex",
229
+ alignItems: "center",
230
+ gap: gap * 2,
231
+ children: children,
232
+ ...rest,
233
+ });
234
+
235
+ // Dynamic theme
236
+ if (propActiveColor === undefined || propInactiveColor === undefined) {
237
+ unsubscribeTheme = subscribeTheme(() => {
238
+ if (propActiveColor === undefined) activeColor = colors.warning;
239
+ if (propInactiveColor === undefined) inactiveColor = colors.border;
240
+ updateStars();
241
+ });
242
+ }
243
+
244
+ ratingContainer.onUnmount(() => {
245
+ if (unsubscribeTheme) unsubscribeTheme();
246
+ });
247
+
248
+ ratingContainer.setValue = setValue;
249
+ ratingContainer.getValue = () => currentValue;
250
+ ratingContainer.updateStars = updateStars;
251
+
252
+ Object.defineProperty(ratingContainer, "value", {
253
+ get: () => currentValue,
254
+ set: (newValue) => setValue(newValue),
255
+ enumerable: true,
256
+ });
257
+
258
+ return ratingContainer;
259
+ };
260
+
261
+ export default Rating;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @file Row.js
3
+ * @description Horizontal flex layout container (default `flex-direction: row`).
4
+ */
5
+ import { WidgetFactory } from "../widget-factory/index.js";
6
+
7
+ export const Row = (props = {}) => {
8
+ const {
9
+ display = "flex",
10
+ flexDirection = "row",
11
+ width = "100%",
12
+ height = "auto",
13
+ boxSizing = "border-box",
14
+ ...rest
15
+ } = props;
16
+
17
+ return WidgetFactory({
18
+ tag: "div",
19
+ widgetName: "Row",
20
+ display: display,
21
+ flexDirection: flexDirection,
22
+ width: width,
23
+ height: height,
24
+ boxSizing: boxSizing,
25
+ ...rest,
26
+ });
27
+ };
28
+
29
+ export default Row;
@@ -0,0 +1,243 @@
1
+ /**
2
+ * @file Skeleton.js
3
+ * @description Loading placeholder (pulse/wave) using globally injected keyframes.
4
+ */
5
+ import { WidgetFactory } from "../widget-factory/index.js";
6
+ import { colors } from "../utils/themes.js";
7
+ import { injectKeyframes } from "../utils/styleInjector.js";
8
+
9
+ export const Skeleton = (props) => {
10
+ const {
11
+ variant = "text", // 'text', 'circular', 'avatar', 'image', 'card', 'listTile', 'button'
12
+ width,
13
+ height,
14
+ borderRadius,
15
+ animation = "pulse", // 'pulse', 'wave', 'none'
16
+ count = 1,
17
+ gap = 8,
18
+
19
+ // Color props for consistency
20
+ bgColor = colors.gray200,
21
+ highlightColor = colors.gray100,
22
+ shimmerColor = colors.gray300,
23
+
24
+ // Animation timing
25
+ pulseDuration = "1.5s",
26
+ waveDuration = "1.5s",
27
+
28
+ ...rest
29
+ } = props;
30
+
31
+ // Default dimensions based on variant
32
+ const getDefaultDimensions = () => {
33
+ switch (variant) {
34
+ case "text":
35
+ return { width: "100%", height: 16, borderRadius: 4 };
36
+ case "circular":
37
+ return { width: 48, height: 48, borderRadius: "50%" };
38
+ case "avatar":
39
+ return { width: 40, height: 40, borderRadius: "50%" };
40
+ case "image":
41
+ return { width: "100%", height: 150, borderRadius: 8 };
42
+ case "card":
43
+ return { width: "100%", height: 120, borderRadius: 12 };
44
+ case "listTile":
45
+ return { width: "100%", height: 56, borderRadius: 8 };
46
+ case "button":
47
+ return { width: 120, height: 36, borderRadius: 24 };
48
+ default:
49
+ return { width: "100%", height: 20, borderRadius: 4 };
50
+ }
51
+ };
52
+
53
+ const defaults = getDefaultDimensions();
54
+ const finalWidth = width !== undefined ? width : defaults.width;
55
+ const finalHeight = height !== undefined ? height : defaults.height;
56
+ const finalBorderRadius =
57
+ borderRadius !== undefined ? borderRadius : defaults.borderRadius;
58
+
59
+ // Animation styles
60
+ const getAnimationStyle = () => {
61
+ if (animation === "pulse") {
62
+ return {
63
+ animation: `skeleton-pulse ${pulseDuration} ease-in-out infinite`,
64
+ };
65
+ }
66
+ if (animation === "wave") {
67
+ return {
68
+ background: `linear-gradient(90deg, ${bgColor} 25%, ${highlightColor} 50%, ${bgColor} 75%)`,
69
+ backgroundSize: "200% 100%",
70
+ animation: `skeleton-wave ${waveDuration} ease-in-out infinite`,
71
+ };
72
+ }
73
+ return {};
74
+ };
75
+
76
+ // Build specific variant structure using WidgetFactory
77
+ const buildVariant = () => {
78
+ // Button variant
79
+ if (variant === "button") {
80
+ return WidgetFactory({
81
+ tag: "div",
82
+ width: typeof finalWidth === "number" ? `${finalWidth}px` : finalWidth,
83
+ height:
84
+ typeof finalHeight === "number" ? `${finalHeight}px` : finalHeight,
85
+ backgroundColor: bgColor,
86
+ borderRadius:
87
+ typeof finalBorderRadius === "number"
88
+ ? `${finalBorderRadius}px`
89
+ : finalBorderRadius,
90
+ ...getAnimationStyle(),
91
+ });
92
+ }
93
+
94
+ // ListTile variant
95
+ if (variant === "listTile") {
96
+ const container = WidgetFactory({
97
+ tag: "div",
98
+ display: "flex",
99
+ alignItems: "center",
100
+ gap: "12px",
101
+ width: "100%",
102
+ });
103
+
104
+ const avatar = WidgetFactory({
105
+ tag: "div",
106
+ width: "40px",
107
+ height: "40px",
108
+ backgroundColor: bgColor,
109
+ borderRadius: "50%",
110
+ flexShrink: 0,
111
+ ...getAnimationStyle(),
112
+ });
113
+ container.appendChild(avatar);
114
+
115
+ const textContainer = WidgetFactory({
116
+ tag: "div",
117
+ flex: 1,
118
+ display: "flex",
119
+ flexDirection: "column",
120
+ gap: "8px",
121
+ });
122
+
123
+ const line1 = WidgetFactory({
124
+ tag: "div",
125
+ width: "80%",
126
+ height: "14px",
127
+ backgroundColor: bgColor,
128
+ borderRadius: "4px",
129
+ ...getAnimationStyle(),
130
+ });
131
+
132
+ const line2 = WidgetFactory({
133
+ tag: "div",
134
+ width: "60%",
135
+ height: "12px",
136
+ backgroundColor: bgColor,
137
+ borderRadius: "4px",
138
+ ...getAnimationStyle(),
139
+ });
140
+
141
+ textContainer.appendChild(line1);
142
+ textContainer.appendChild(line2);
143
+ container.appendChild(textContainer);
144
+
145
+ return container;
146
+ }
147
+
148
+ // Card variant
149
+ if (variant === "card") {
150
+ const container = WidgetFactory({
151
+ tag: "div",
152
+ display: "flex",
153
+ flexDirection: "column",
154
+ gap: "12px",
155
+ width: "100%",
156
+ });
157
+
158
+ const imageArea = WidgetFactory({
159
+ tag: "div",
160
+ width: "100%",
161
+ height: "120px",
162
+ backgroundColor: bgColor,
163
+ borderRadius: "8px",
164
+ ...getAnimationStyle(),
165
+ });
166
+ container.appendChild(imageArea);
167
+
168
+ const textLines = WidgetFactory({
169
+ tag: "div",
170
+ display: "flex",
171
+ flexDirection: "column",
172
+ gap: "8px",
173
+ padding: "0 8px",
174
+ });
175
+
176
+ const line1 = WidgetFactory({
177
+ tag: "div",
178
+ width: "90%",
179
+ height: "16px",
180
+ backgroundColor: bgColor,
181
+ borderRadius: "4px",
182
+ ...getAnimationStyle(),
183
+ });
184
+
185
+ const line2 = WidgetFactory({
186
+ tag: "div",
187
+ width: "70%",
188
+ height: "12px",
189
+ backgroundColor: bgColor,
190
+ borderRadius: "4px",
191
+ ...getAnimationStyle(),
192
+ });
193
+
194
+ textLines.appendChild(line1);
195
+ textLines.appendChild(line2);
196
+ container.appendChild(textLines);
197
+
198
+ return container;
199
+ }
200
+
201
+ // Simple variants (text, circular, avatar, image)
202
+ return WidgetFactory({
203
+ tag: "div",
204
+ width: typeof finalWidth === "number" ? `${finalWidth}px` : finalWidth,
205
+ height:
206
+ typeof finalHeight === "number" ? `${finalHeight}px` : finalHeight,
207
+ backgroundColor: bgColor,
208
+ borderRadius:
209
+ typeof finalBorderRadius === "number"
210
+ ? `${finalBorderRadius}px`
211
+ : finalBorderRadius,
212
+ ...getAnimationStyle(),
213
+ });
214
+ };
215
+
216
+ // Createste multiple items container using WidgetFactory
217
+ const container = WidgetFactory({
218
+ tag: "div",
219
+ widgetName: "Skeleton",
220
+ display: "flex",
221
+ flexDirection: "column",
222
+ gap: typeof gap === "number" ? `${gap}px` : gap,
223
+ width: "100%",
224
+ });
225
+
226
+ for (let i = 0; i < count; i++) {
227
+ container.appendChild(buildVariant());
228
+ }
229
+
230
+ injectKeyframes(
231
+ "skeleton-pulse",
232
+ "0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; }",
233
+ "skeleton-styles",
234
+ );
235
+ injectKeyframes(
236
+ "skeleton-wave",
237
+ "0% { background-position: 200% 0; } 100% { background-position: -200% 0; }",
238
+ );
239
+
240
+ return container;
241
+ };
242
+
243
+ export default Skeleton;