@mhome/ui 0.1.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.
Files changed (53) hide show
  1. package/README.md +188 -0
  2. package/dist/index.cjs.js +9 -0
  3. package/dist/index.cjs.js.map +1 -0
  4. package/dist/index.css +2 -0
  5. package/dist/index.esm.js +9 -0
  6. package/dist/index.esm.js.map +1 -0
  7. package/package.json +54 -0
  8. package/src/common/adaptive-theme-provider.js +19 -0
  9. package/src/components/accordion.jsx +306 -0
  10. package/src/components/alert.jsx +137 -0
  11. package/src/components/app-bar.jsx +105 -0
  12. package/src/components/autocomplete.jsx +347 -0
  13. package/src/components/avatar.jsx +160 -0
  14. package/src/components/box.jsx +165 -0
  15. package/src/components/button.jsx +104 -0
  16. package/src/components/card.jsx +156 -0
  17. package/src/components/checkbox.jsx +63 -0
  18. package/src/components/chip.jsx +137 -0
  19. package/src/components/collapse.jsx +188 -0
  20. package/src/components/container.jsx +67 -0
  21. package/src/components/date-picker.jsx +528 -0
  22. package/src/components/dialog-content-text.jsx +27 -0
  23. package/src/components/dialog.jsx +584 -0
  24. package/src/components/divider.jsx +192 -0
  25. package/src/components/drawer.jsx +255 -0
  26. package/src/components/form-control-label.jsx +89 -0
  27. package/src/components/form-group.jsx +32 -0
  28. package/src/components/form-label.jsx +54 -0
  29. package/src/components/grid.jsx +135 -0
  30. package/src/components/icon-button.jsx +101 -0
  31. package/src/components/index.js +78 -0
  32. package/src/components/input-adornment.jsx +43 -0
  33. package/src/components/input-label.jsx +55 -0
  34. package/src/components/list.jsx +239 -0
  35. package/src/components/menu.jsx +370 -0
  36. package/src/components/paper.jsx +173 -0
  37. package/src/components/radio-group.jsx +76 -0
  38. package/src/components/radio.jsx +108 -0
  39. package/src/components/select.jsx +308 -0
  40. package/src/components/slider.jsx +382 -0
  41. package/src/components/stack.jsx +110 -0
  42. package/src/components/table.jsx +243 -0
  43. package/src/components/tabs.jsx +363 -0
  44. package/src/components/text-field.jsx +289 -0
  45. package/src/components/toggle-button.jsx +209 -0
  46. package/src/components/toolbar.jsx +48 -0
  47. package/src/components/tooltip.jsx +127 -0
  48. package/src/components/typography.jsx +77 -0
  49. package/src/global-state.js +29 -0
  50. package/src/index.css +110 -0
  51. package/src/index.js +6 -0
  52. package/src/lib/useMediaQuery.js +37 -0
  53. package/src/lib/utils.js +113 -0
@@ -0,0 +1,382 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/utils";
3
+
4
+ const Slider = React.forwardRef(
5
+ (
6
+ {
7
+ value,
8
+ onChange,
9
+ onChangeCommitted,
10
+ min = 0,
11
+ max = 100,
12
+ step = 1,
13
+ disabled = false,
14
+ orientation = "horizontal",
15
+ className,
16
+ style,
17
+ sx,
18
+ "aria-label": ariaLabel,
19
+ "aria-labelledby": ariaLabelledBy,
20
+ "aria-valuetext": ariaValueText,
21
+ ...props
22
+ },
23
+ ref
24
+ ) => {
25
+ const containerRef = React.useRef(null);
26
+ const draggingRef = React.useRef(false);
27
+ const [localValue, setLocalValue] = React.useState(value);
28
+
29
+ // Convert sx to style if provided
30
+ const sxStyles = React.useMemo(() => {
31
+ if (!sx) return { container: {} };
32
+ const sxObj = typeof sx === "function" ? sx({}) : sx;
33
+ // Convert MUI sx object to style object
34
+ const converted = { container: {} };
35
+
36
+ // Container-level styles (position, top, transform, width, left, etc.)
37
+ if (sxObj.position) converted.container.position = sxObj.position;
38
+ if (sxObj.top !== undefined) converted.container.top = sxObj.top;
39
+ if (sxObj.left !== undefined) converted.container.left = sxObj.left;
40
+ if (sxObj.width) converted.container.width = sxObj.width;
41
+ if (sxObj.height) converted.container.height = sxObj.height;
42
+ if (sxObj.transform) converted.container.transform = sxObj.transform;
43
+
44
+ // Internal component styles
45
+ if (sxObj["& .MuiSlider-rail"]) {
46
+ converted.rail = sxObj["& .MuiSlider-rail"];
47
+ }
48
+ if (sxObj["& .MuiSlider-track"]) {
49
+ converted.track = sxObj["& .MuiSlider-track"];
50
+ }
51
+ if (sxObj["& .MuiSlider-thumb"]) {
52
+ converted.thumb = sxObj["& .MuiSlider-thumb"];
53
+ }
54
+ return converted;
55
+ }, [sx, localValue]);
56
+
57
+ // Sync localValue with value prop
58
+ React.useEffect(() => {
59
+ if (!draggingRef.current) {
60
+ setLocalValue(value);
61
+ }
62
+ }, [value]);
63
+
64
+ const isVertical = orientation === "vertical";
65
+ const percent = Math.max(0, Math.min(1, (localValue - min) / (max - min)));
66
+
67
+ const handleMouseDown = (e) => {
68
+ if (disabled) return;
69
+ e.preventDefault();
70
+ e.stopPropagation();
71
+ draggingRef.current = true;
72
+ handleMove(e);
73
+ // Use capture phase to ensure we get the events
74
+ document.addEventListener("mousemove", handleMouseMove, {
75
+ capture: true,
76
+ });
77
+ document.addEventListener("mouseup", handleMouseUp, { capture: true });
78
+ };
79
+
80
+ const handleTouchStart = (e) => {
81
+ if (disabled) return;
82
+ e.preventDefault();
83
+ e.stopPropagation();
84
+ draggingRef.current = true;
85
+ handleMove(e.touches[0]);
86
+ // Use capture phase to ensure we get the events
87
+ document.addEventListener("touchmove", handleTouchMove, {
88
+ capture: true,
89
+ passive: false,
90
+ });
91
+ document.addEventListener("touchend", handleTouchEnd, { capture: true });
92
+ };
93
+
94
+ const handleMouseMove = (e) => {
95
+ if (!draggingRef.current) return;
96
+ e.preventDefault();
97
+ handleMove(e);
98
+ };
99
+
100
+ const handleTouchMove = (e) => {
101
+ if (!draggingRef.current) return;
102
+ e.preventDefault();
103
+ handleMove(e.touches[0]);
104
+ };
105
+
106
+ const handleMouseUp = () => {
107
+ if (draggingRef.current && onChangeCommitted) {
108
+ onChangeCommitted(null, localValue);
109
+ }
110
+ draggingRef.current = false;
111
+ document.removeEventListener("mousemove", handleMouseMove, {
112
+ capture: true,
113
+ });
114
+ document.removeEventListener("mouseup", handleMouseUp, { capture: true });
115
+ };
116
+
117
+ const handleTouchEnd = () => {
118
+ if (draggingRef.current && onChangeCommitted) {
119
+ onChangeCommitted(null, localValue);
120
+ }
121
+ draggingRef.current = false;
122
+ document.removeEventListener("touchmove", handleTouchMove, {
123
+ capture: true,
124
+ });
125
+ document.removeEventListener("touchend", handleTouchEnd, {
126
+ capture: true,
127
+ });
128
+ };
129
+
130
+ const handleMove = (e) => {
131
+ if (!containerRef.current) return;
132
+ const rect = containerRef.current.getBoundingClientRect();
133
+
134
+ if (isVertical) {
135
+ let y = rect.bottom - e.clientY;
136
+ y = Math.max(0, Math.min(rect.height, y));
137
+ const newPercent = y / rect.height;
138
+ const newValue = Math.round(newPercent * (max - min) + min);
139
+ const clampedValue = Math.max(min, Math.min(max, newValue));
140
+ setLocalValue(clampedValue);
141
+ if (onChange) onChange(null, clampedValue);
142
+ } else {
143
+ let x = e.clientX - rect.left;
144
+ x = Math.max(0, Math.min(rect.width, x));
145
+ const newPercent = x / rect.width;
146
+ const newValue = Math.round(newPercent * (max - min) + min);
147
+ const clampedValue = Math.max(min, Math.min(max, newValue));
148
+ setLocalValue(clampedValue);
149
+ if (onChange) onChange(null, clampedValue);
150
+ }
151
+ };
152
+
153
+ const railStyle = {
154
+ ...(isVertical
155
+ ? {
156
+ width: "100%",
157
+ height: "100%",
158
+ borderRadius: sxStyles.rail?.borderRadius || "10px",
159
+ backgroundColor:
160
+ sxStyles.rail?.backgroundColor || "hsl(var(--muted))",
161
+ border: sxStyles.rail?.border || "none",
162
+ position: "relative",
163
+ zIndex: 0,
164
+ }
165
+ : {
166
+ width: "100%",
167
+ height: sxStyles.rail?.height || "4px",
168
+ borderRadius: sxStyles.rail?.borderRadius || "2px",
169
+ backgroundColor:
170
+ sxStyles.rail?.backgroundColor || "hsl(var(--muted))",
171
+ border: sxStyles.rail?.border || "none",
172
+ position: "relative",
173
+ zIndex: 0,
174
+ }),
175
+ };
176
+
177
+ // Get track display value (handle dynamic display based on value)
178
+ const getTrackDisplay = () => {
179
+ if (sxStyles.track?.display !== undefined) {
180
+ // Handle function or conditional display
181
+ if (typeof sxStyles.track.display === "function") {
182
+ return sxStyles.track.display(localValue) ? "block" : "none";
183
+ }
184
+ return sxStyles.track.display;
185
+ }
186
+ // Default: hide track when value is 0 (for vertical sliders)
187
+ return isVertical && localValue === 0 ? "none" : "block";
188
+ };
189
+
190
+ const trackStyle = {
191
+ ...(isVertical
192
+ ? {
193
+ position: "absolute",
194
+ bottom: 0,
195
+ left: 0,
196
+ width: "100%",
197
+ height: `${percent * 100}%`,
198
+ borderRadius: sxStyles.track?.borderRadius || "10px",
199
+ backgroundColor:
200
+ sxStyles.track?.backgroundColor || "hsl(var(--primary))",
201
+ display: getTrackDisplay(),
202
+ border: sxStyles.track?.border || "none",
203
+ zIndex: 1,
204
+ pointerEvents: "none",
205
+ }
206
+ : {
207
+ position: "absolute",
208
+ top: "50%",
209
+ left: 0,
210
+ transform: "translateY(-50%)",
211
+ width: `${percent * 100}%`,
212
+ height: sxStyles.track?.height || "4px",
213
+ borderRadius: sxStyles.track?.borderRadius || "2px",
214
+ backgroundColor:
215
+ sxStyles.track?.backgroundColor || "hsl(var(--primary))",
216
+ border: sxStyles.track?.border || "none",
217
+ zIndex: 1,
218
+ pointerEvents: "none",
219
+ }),
220
+ };
221
+
222
+ // Get thumb display value (handle dynamic display based on value)
223
+ const getThumbDisplay = () => {
224
+ if (sxStyles.thumb?.display !== undefined) {
225
+ // Handle function or conditional display
226
+ if (typeof sxStyles.thumb.display === "function") {
227
+ return sxStyles.thumb.display(localValue) ? "block" : "none";
228
+ }
229
+ return sxStyles.thumb.display;
230
+ }
231
+ // Default: hide thumb when value <= 3 (for vertical sliders)
232
+ return isVertical && localValue <= 3 ? "none" : "block";
233
+ };
234
+
235
+ const thumbStyle = {
236
+ ...(isVertical
237
+ ? {
238
+ position: "absolute",
239
+ left: "50%",
240
+ bottom: `${percent * 100}%`,
241
+ transform:
242
+ sxStyles.thumb?.transform || "translateX(-50%) translateY(50%)",
243
+ width: sxStyles.thumb?.width || 20,
244
+ height: sxStyles.thumb?.height || 2,
245
+ backgroundColor: sxStyles.thumb?.backgroundColor || "#ffffff",
246
+ boxShadow:
247
+ sxStyles.thumb?.boxShadow || "0 0 4px rgba(0, 0, 0, 0.3)",
248
+ borderRadius: sxStyles.thumb?.borderRadius || "1px",
249
+ marginBottom: sxStyles.thumb?.marginBottom || "-4px",
250
+ display: getThumbDisplay(),
251
+ cursor: disabled ? "default" : "pointer",
252
+ zIndex: 2,
253
+ pointerEvents: "auto",
254
+ }
255
+ : {
256
+ position: "absolute",
257
+ top: "50%",
258
+ left: `${percent * 100}%`,
259
+ transform: "translate(-50%, -50%)",
260
+ width: sxStyles.thumb?.width || 16,
261
+ height: sxStyles.thumb?.height || 16,
262
+ backgroundColor:
263
+ sxStyles.thumb?.backgroundColor || "hsl(var(--primary))",
264
+ boxShadow: sxStyles.thumb?.boxShadow || "0 2px 4px rgba(0,0,0,0.2)",
265
+ borderRadius: sxStyles.thumb?.borderRadius || "50%",
266
+ border: "2px solid hsl(var(--background))",
267
+ cursor: disabled ? "default" : "pointer",
268
+ zIndex: 2,
269
+ pointerEvents: "auto",
270
+ }),
271
+ };
272
+
273
+ const sliderId = React.useId();
274
+ const currentValue = localValue !== undefined ? localValue : value;
275
+
276
+ return (
277
+ <div
278
+ ref={containerRef}
279
+ role="slider"
280
+ id={sliderId}
281
+ aria-label={ariaLabel}
282
+ aria-labelledby={ariaLabelledBy}
283
+ aria-valuemin={min}
284
+ aria-valuemax={max}
285
+ aria-valuenow={currentValue}
286
+ aria-valuetext={ariaValueText || String(currentValue)}
287
+ aria-orientation={orientation}
288
+ aria-disabled={disabled}
289
+ tabIndex={disabled ? -1 : 0}
290
+ className={cn(
291
+ "relative select-none focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
292
+ isVertical ? "h-full w-full" : "w-full",
293
+ className
294
+ )}
295
+ style={{
296
+ ...(isVertical ? { height: "100%", width: "100%" } : {}),
297
+ ...sxStyles.container,
298
+ ...style,
299
+ // Ensure slider can receive pointer events
300
+ pointerEvents: disabled ? "none" : "auto",
301
+ // Prevent touch scrolling that might interfere with slider dragging
302
+ touchAction: disabled ? "auto" : "none",
303
+ // Ensure container is above background layers
304
+ zIndex: 1,
305
+ }}
306
+ onMouseDown={handleMouseDown}
307
+ onTouchStart={handleTouchStart}
308
+ onKeyDown={(e) => {
309
+ if (disabled) return;
310
+ let newValue = currentValue;
311
+ const stepSize = step || 1;
312
+
313
+ if (orientation === "horizontal") {
314
+ if (e.key === "ArrowRight" || e.key === "ArrowUp") {
315
+ e.preventDefault();
316
+ newValue = Math.min(max, currentValue + stepSize);
317
+ } else if (e.key === "ArrowLeft" || e.key === "ArrowDown") {
318
+ e.preventDefault();
319
+ newValue = Math.max(min, currentValue - stepSize);
320
+ } else if (e.key === "Home") {
321
+ e.preventDefault();
322
+ newValue = min;
323
+ } else if (e.key === "End") {
324
+ e.preventDefault();
325
+ newValue = max;
326
+ }
327
+ } else {
328
+ if (e.key === "ArrowUp") {
329
+ e.preventDefault();
330
+ newValue = Math.min(max, currentValue + stepSize);
331
+ } else if (e.key === "ArrowDown") {
332
+ e.preventDefault();
333
+ newValue = Math.max(min, currentValue - stepSize);
334
+ } else if (e.key === "Home") {
335
+ e.preventDefault();
336
+ newValue = min;
337
+ } else if (e.key === "End") {
338
+ e.preventDefault();
339
+ newValue = max;
340
+ }
341
+ }
342
+
343
+ if (newValue !== currentValue) {
344
+ setLocalValue(newValue);
345
+ if (onChange) {
346
+ onChange(newValue);
347
+ }
348
+ }
349
+ }}
350
+ {...props}
351
+ >
352
+ {/* Rail */}
353
+ <div style={railStyle} />
354
+ {/* Track */}
355
+ <div style={trackStyle} />
356
+ {/* Thumb */}
357
+ <div
358
+ style={thumbStyle}
359
+ // Let container handle all mouse/touch events for consistency
360
+ onMouseEnter={(e) => {
361
+ if (!disabled && sxStyles.thumb?.["&:hover"]) {
362
+ e.currentTarget.style.boxShadow =
363
+ sxStyles.thumb["&:hover"].boxShadow ||
364
+ e.currentTarget.style.boxShadow;
365
+ }
366
+ }}
367
+ onMouseLeave={(e) => {
368
+ e.currentTarget.style.boxShadow =
369
+ sxStyles.thumb?.boxShadow ||
370
+ (isVertical
371
+ ? "0 0 4px rgba(0, 0, 0, 0.3)"
372
+ : "0 1px 3px rgba(0,0,0,0.4)");
373
+ }}
374
+ />
375
+ </div>
376
+ );
377
+ }
378
+ );
379
+
380
+ Slider.displayName = "Slider";
381
+
382
+ export { Slider };
@@ -0,0 +1,110 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/utils";
3
+
4
+ const Stack = React.forwardRef(
5
+ (
6
+ {
7
+ className,
8
+ direction = "column",
9
+ spacing = 0,
10
+ justifyContent,
11
+ alignItems,
12
+ flexWrap,
13
+ gap,
14
+ sx,
15
+ style,
16
+ children,
17
+ ...props
18
+ },
19
+ ref
20
+ ) => {
21
+ // Convert MUI spacing to pixels (MUI spacing unit = 8px)
22
+ const spacingPx = typeof spacing === "number" ? spacing * 8 : 0;
23
+
24
+ // Use gap if provided, otherwise use spacing
25
+ const gapValue = gap !== undefined ? gap : spacingPx;
26
+
27
+ // Convert sx prop to style if provided
28
+ const sxStyles = React.useMemo(() => {
29
+ if (!sx) return {};
30
+ const sxObj = typeof sx === "function" ? sx({}) : sx;
31
+ return sxObj;
32
+ }, [sx]);
33
+
34
+ // Build flex direction class
35
+ const directionClass =
36
+ direction === "row"
37
+ ? "flex-row"
38
+ : direction === "row-reverse"
39
+ ? "flex-row-reverse"
40
+ : direction === "column-reverse"
41
+ ? "column-reverse"
42
+ : "flex-col";
43
+
44
+ // Build flex wrap class
45
+ const wrapClass =
46
+ flexWrap === "wrap"
47
+ ? "flex-wrap"
48
+ : flexWrap === "nowrap"
49
+ ? "flex-nowrap"
50
+ : "";
51
+
52
+ // Build justifyContent class
53
+ const justifyContentClass =
54
+ justifyContent === "flex-start"
55
+ ? "justify-start"
56
+ : justifyContent === "flex-end"
57
+ ? "justify-end"
58
+ : justifyContent === "center"
59
+ ? "justify-center"
60
+ : justifyContent === "space-between"
61
+ ? "justify-between"
62
+ : justifyContent === "space-around"
63
+ ? "justify-around"
64
+ : justifyContent === "space-evenly"
65
+ ? "justify-evenly"
66
+ : "";
67
+
68
+ // Build alignItems class
69
+ const alignItemsClass =
70
+ alignItems === "flex-start"
71
+ ? "items-start"
72
+ : alignItems === "flex-end"
73
+ ? "items-end"
74
+ : alignItems === "center"
75
+ ? "items-center"
76
+ : alignItems === "baseline"
77
+ ? "items-baseline"
78
+ : alignItems === "stretch"
79
+ ? "items-stretch"
80
+ : "";
81
+
82
+ const mergedStyle = {
83
+ display: "flex",
84
+ ...(gapValue && { gap: `${gapValue}px` }),
85
+ ...sxStyles,
86
+ ...style,
87
+ };
88
+
89
+ return (
90
+ <div
91
+ ref={ref}
92
+ className={cn(
93
+ directionClass,
94
+ wrapClass,
95
+ justifyContentClass,
96
+ alignItemsClass,
97
+ className
98
+ )}
99
+ style={mergedStyle}
100
+ {...props}
101
+ >
102
+ {children}
103
+ </div>
104
+ );
105
+ }
106
+ );
107
+
108
+ Stack.displayName = "Stack";
109
+
110
+ export { Stack };