@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,584 @@
1
+ import * as React from "react";
2
+ import { createPortal } from "react-dom";
3
+ import { cn, spacingToPx, useIsDarkMode } from "../lib/utils";
4
+ import { getBackground } from "../common/adaptive-theme-provider";
5
+ import { useRecoilValue, backgroundTypeState } from "../global-state";
6
+
7
+ const Dialog = ({
8
+ open,
9
+ onClose,
10
+ children,
11
+ variant,
12
+ maxWidth,
13
+ fullWidth,
14
+ fullScreen = false,
15
+ PaperProps,
16
+ "aria-labelledby": ariaLabelledBy,
17
+ "aria-describedby": ariaDescribedBy,
18
+ ...props
19
+ }) => {
20
+ const dialogId = React.useId();
21
+ const actualMode = useIsDarkMode();
22
+ const backgroundType = useRecoilValue(backgroundTypeState);
23
+
24
+ // All hooks must be called before any early returns
25
+ // This ensures hooks are called in the same order on every render
26
+ // Merge sx styles from PaperProps if provided
27
+ const paperSxStyles = React.useMemo(() => {
28
+ if (!PaperProps?.sx) return {};
29
+ const sxObj =
30
+ typeof PaperProps.sx === "function"
31
+ ? PaperProps.sx({}) // Empty theme object for compatibility
32
+ : PaperProps.sx;
33
+ return sxObj;
34
+ }, [PaperProps?.sx]);
35
+
36
+ if (!open) return null;
37
+
38
+ const maxWidthClass =
39
+ {
40
+ xs: "max-w-xs",
41
+ sm: "max-w-sm",
42
+ md: "max-w-md",
43
+ lg: "max-w-lg",
44
+ xl: "max-w-xl",
45
+ }[maxWidth] || "max-w-lg";
46
+
47
+ // Get pops variant styles from theme
48
+ const popsStyles =
49
+ variant === "pops"
50
+ ? {
51
+ minWidth: "300px",
52
+ maxWidth: "800px",
53
+ }
54
+ : {};
55
+
56
+ // Drawer variant styles
57
+ const drawerStyles =
58
+ variant === "drawer"
59
+ ? {
60
+ margin: 0,
61
+ maxHeight: "100vh",
62
+ height: "100vh",
63
+ borderRadius: "16px 0 0 16px",
64
+ }
65
+ : {};
66
+
67
+ // For drawer variant, use background gradient if available, otherwise use standard background
68
+ const paperBg =
69
+ variant === "drawer"
70
+ ? getBackground(backgroundType, actualMode === "dark")
71
+ : null;
72
+ const isGradient =
73
+ paperBg &&
74
+ typeof paperBg === "string" &&
75
+ (paperBg.startsWith("linear-gradient") ||
76
+ paperBg.startsWith("radial-gradient") ||
77
+ paperBg.startsWith("conic-gradient"));
78
+
79
+ const borderRadius =
80
+ variant === "pops"
81
+ ? "16px"
82
+ : variant === "drawer"
83
+ ? "16px 0 0 16px"
84
+ : "8px";
85
+
86
+ // Handle PaperProps (MUI compatibility)
87
+ const paperPropsStyle = PaperProps?.style || {};
88
+ const paperPropsClassName = PaperProps?.className || "";
89
+
90
+ // Determine container alignment based on variant
91
+ const containerClass =
92
+ variant === "drawer"
93
+ ? "flex items-stretch justify-end"
94
+ : fullScreen
95
+ ? "flex items-stretch"
96
+ : "flex items-center justify-center";
97
+
98
+ // Get z-index from CSS variable or use default
99
+ const zIndex =
100
+ variant === "drawer"
101
+ ? parseInt(
102
+ getComputedStyle(document.documentElement).getPropertyValue(
103
+ "--z-index-drawer"
104
+ ) || "1300",
105
+ 10
106
+ )
107
+ : parseInt(
108
+ getComputedStyle(document.documentElement).getPropertyValue(
109
+ "--z-index-modal"
110
+ ) || "1300",
111
+ 10
112
+ );
113
+
114
+ // Render dialog content using Portal to body to avoid parent container constraints
115
+ const dialogContent = (
116
+ <>
117
+ {/* Backdrop - more opaque for better separation */}
118
+ <div
119
+ className={cn(
120
+ "fixed inset-0",
121
+ actualMode === "dark" ? "bg-black/80" : "bg-black/60"
122
+ )}
123
+ style={{
124
+ zIndex: zIndex - 1,
125
+ pointerEvents: "auto",
126
+ }}
127
+ onClick={(e) => {
128
+ if (onClose) onClose(e, "backdropClick");
129
+ }}
130
+ />
131
+ {/* Dialog content - fully opaque, above backdrop */}
132
+ <div
133
+ className={cn("fixed inset-0", containerClass)}
134
+ style={{
135
+ zIndex: zIndex,
136
+ pointerEvents: "none",
137
+ }}
138
+ {...props}
139
+ >
140
+ <div
141
+ role="dialog"
142
+ aria-modal="true"
143
+ aria-labelledby={ariaLabelledBy || `${dialogId}-title`}
144
+ aria-describedby={ariaDescribedBy || `${dialogId}-description`}
145
+ className={cn(
146
+ "relative shadow-2xl",
147
+ fullScreen || variant === "drawer"
148
+ ? "h-full w-full"
149
+ : "w-full max-w-[calc(100%-32px)]",
150
+ fullScreen || variant === "drawer" ? "" : "mx-4",
151
+ !fullScreen && variant !== "drawer" && maxWidthClass,
152
+ fullWidth && !fullScreen && variant !== "drawer" && "max-w-full",
153
+ variant === "drawer" && !isGradient && "bg-background",
154
+ !isGradient && variant !== "drawer" && "bg-card",
155
+ paperPropsClassName
156
+ )}
157
+ style={{
158
+ // Use background for gradients, className for solid colors
159
+ ...(isGradient ? { background: paperBg } : {}),
160
+ borderRadius: fullScreen ? 0 : borderRadius,
161
+ position: "relative",
162
+ opacity: 1,
163
+ pointerEvents: "auto",
164
+ touchAction: "none",
165
+ ...popsStyles,
166
+ ...drawerStyles,
167
+ ...paperSxStyles,
168
+ ...paperPropsStyle,
169
+ }}
170
+ >
171
+ {children}
172
+ </div>
173
+ </div>
174
+ </>
175
+ );
176
+
177
+ // Use Portal to render dialog to body, ensuring it's not constrained by parent containers
178
+ return typeof document !== "undefined"
179
+ ? createPortal(dialogContent, document.body)
180
+ : null;
181
+ };
182
+
183
+ const DialogContent = React.forwardRef(
184
+ ({ className, children, style, sx, ...props }, ref) => {
185
+ const actualMode = useIsDarkMode();
186
+ const backgroundType = useRecoilValue(backgroundTypeState);
187
+ const bgColor = getBackground(backgroundType, actualMode === "dark");
188
+ const isGradient =
189
+ bgColor &&
190
+ typeof bgColor === "string" &&
191
+ (bgColor.startsWith("linear-gradient") ||
192
+ bgColor.startsWith("radial-gradient") ||
193
+ bgColor.startsWith("conic-gradient"));
194
+
195
+ // Convert sx prop to style if provided, handling MUI spacing
196
+ const sxStyles = React.useMemo(() => {
197
+ if (!sx) return {};
198
+ const sxObj = typeof sx === "function" ? sx({}) : sx; // Empty theme object for compatibility
199
+
200
+ // Convert MUI spacing properties to CSS
201
+ const converted = { ...sxObj };
202
+
203
+ // Padding shortcuts
204
+ if (converted.p !== undefined) {
205
+ converted.padding = spacingToPx(converted.p);
206
+ delete converted.p;
207
+ }
208
+ if (converted.px !== undefined) {
209
+ converted.paddingLeft = spacingToPx(converted.px);
210
+ converted.paddingRight = spacingToPx(converted.px);
211
+ delete converted.px;
212
+ }
213
+ if (converted.py !== undefined) {
214
+ converted.paddingTop = spacingToPx(converted.py);
215
+ converted.paddingBottom = spacingToPx(converted.py);
216
+ delete converted.py;
217
+ }
218
+ if (converted.pt !== undefined) {
219
+ converted.paddingTop = spacingToPx(converted.pt);
220
+ delete converted.pt;
221
+ }
222
+ if (converted.pb !== undefined) {
223
+ converted.paddingBottom = spacingToPx(converted.pb);
224
+ delete converted.pb;
225
+ }
226
+ if (converted.pl !== undefined) {
227
+ converted.paddingLeft = spacingToPx(converted.pl);
228
+ delete converted.pl;
229
+ }
230
+ if (converted.pr !== undefined) {
231
+ converted.paddingRight = spacingToPx(converted.pr);
232
+ delete converted.pr;
233
+ }
234
+
235
+ // Margin shortcuts
236
+ if (converted.m !== undefined) {
237
+ converted.margin = spacingToPx(converted.m);
238
+ delete converted.m;
239
+ }
240
+ if (converted.mx !== undefined) {
241
+ converted.marginLeft = spacingToPx(converted.mx);
242
+ converted.marginRight = spacingToPx(converted.mx);
243
+ delete converted.mx;
244
+ }
245
+ if (converted.my !== undefined) {
246
+ converted.marginTop = spacingToPx(converted.my);
247
+ converted.marginBottom = spacingToPx(converted.my);
248
+ delete converted.my;
249
+ }
250
+ if (converted.mt !== undefined) {
251
+ converted.marginTop = spacingToPx(converted.mt);
252
+ delete converted.mt;
253
+ }
254
+ if (converted.mb !== undefined) {
255
+ converted.marginBottom = spacingToPx(converted.mb);
256
+ delete converted.mb;
257
+ }
258
+ if (converted.ml !== undefined) {
259
+ converted.marginLeft = spacingToPx(converted.ml);
260
+ delete converted.ml;
261
+ }
262
+ if (converted.mr !== undefined) {
263
+ converted.marginRight = spacingToPx(converted.mr);
264
+ delete converted.mr;
265
+ }
266
+
267
+ // Gap
268
+ if (converted.gap !== undefined) {
269
+ converted.gap = spacingToPx(converted.gap);
270
+ }
271
+
272
+ return converted;
273
+ }, [sx]);
274
+
275
+ // Extract padding from style to allow override
276
+ const {
277
+ padding,
278
+ paddingTop,
279
+ paddingBottom,
280
+ paddingLeft,
281
+ paddingRight,
282
+ backgroundColor,
283
+ ...restStyle
284
+ } = style || {};
285
+
286
+ const hasPaddingInStyle =
287
+ padding !== undefined ||
288
+ paddingTop !== undefined ||
289
+ paddingBottom !== undefined ||
290
+ paddingLeft !== undefined ||
291
+ paddingRight !== undefined;
292
+
293
+ // Default padding class, but allow override
294
+ const defaultPaddingClass = hasPaddingInStyle ? "" : "p-6";
295
+
296
+ return (
297
+ <div
298
+ ref={ref}
299
+ className={cn(
300
+ "text-left rounded-b-[inherit]",
301
+ defaultPaddingClass,
302
+ "p-6",
303
+ className
304
+ )}
305
+ style={{
306
+ // Only set background if explicitly provided, otherwise inherit from parent
307
+ ...(backgroundColor !== undefined
308
+ ? backgroundColor.startsWith("linear-gradient") ||
309
+ backgroundColor.startsWith("radial-gradient") ||
310
+ backgroundColor.startsWith("conic-gradient")
311
+ ? { background: backgroundColor }
312
+ : { backgroundColor }
313
+ : {}),
314
+ padding: padding,
315
+ paddingTop: paddingTop,
316
+ paddingBottom: paddingBottom,
317
+ paddingLeft: paddingLeft,
318
+ paddingRight: paddingRight,
319
+ opacity: 1,
320
+ pointerEvents: "auto",
321
+ ...sxStyles,
322
+ ...restStyle,
323
+ }}
324
+ {...props}
325
+ >
326
+ {children}
327
+ </div>
328
+ );
329
+ }
330
+ );
331
+ DialogContent.displayName = "DialogContent";
332
+
333
+ const DialogHeader = ({ className, ...props }) => (
334
+ <div
335
+ className={cn(
336
+ "flex flex-col space-y-1.5 text-center sm:text-left px-6 pt-6",
337
+ className
338
+ )}
339
+ {...props}
340
+ />
341
+ );
342
+ DialogHeader.displayName = "DialogHeader";
343
+
344
+ const DialogFooter = ({ className, ...props }) => (
345
+ <div
346
+ className={cn(
347
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 px-6 pb-6",
348
+ className
349
+ )}
350
+ {...props}
351
+ />
352
+ );
353
+ DialogFooter.displayName = "DialogFooter";
354
+
355
+ const DialogTitle = React.forwardRef(
356
+ ({ className, style, sx, ...props }, ref) => {
357
+ const actualMode = useIsDarkMode();
358
+ const backgroundType = useRecoilValue(backgroundTypeState);
359
+ const bgColor = getBackground(backgroundType, actualMode === "dark");
360
+ const isGradient =
361
+ bgColor &&
362
+ typeof bgColor === "string" &&
363
+ (bgColor.startsWith("linear-gradient") ||
364
+ bgColor.startsWith("radial-gradient") ||
365
+ bgColor.startsWith("conic-gradient"));
366
+
367
+ // Convert sx prop to style if provided, handling MUI spacing
368
+ const sxStyles = React.useMemo(() => {
369
+ if (!sx) return {};
370
+ const sxObj = typeof sx === "function" ? sx({}) : sx; // Empty theme object for compatibility
371
+
372
+ // Convert MUI spacing properties to CSS
373
+ const converted = { ...sxObj };
374
+
375
+ // Padding shortcuts
376
+ if (converted.p !== undefined) {
377
+ converted.padding = spacingToPx(converted.p);
378
+ delete converted.p;
379
+ }
380
+ if (converted.px !== undefined) {
381
+ converted.paddingLeft = spacingToPx(converted.px);
382
+ converted.paddingRight = spacingToPx(converted.px);
383
+ delete converted.px;
384
+ }
385
+ if (converted.py !== undefined) {
386
+ converted.paddingTop = spacingToPx(converted.py);
387
+ converted.paddingBottom = spacingToPx(converted.py);
388
+ delete converted.py;
389
+ }
390
+ if (converted.pt !== undefined) {
391
+ converted.paddingTop = spacingToPx(converted.pt);
392
+ delete converted.pt;
393
+ }
394
+ if (converted.pb !== undefined) {
395
+ converted.paddingBottom = spacingToPx(converted.pb);
396
+ delete converted.pb;
397
+ }
398
+ if (converted.pl !== undefined) {
399
+ converted.paddingLeft = spacingToPx(converted.pl);
400
+ delete converted.pl;
401
+ }
402
+ if (converted.pr !== undefined) {
403
+ converted.paddingRight = spacingToPx(converted.pr);
404
+ delete converted.pr;
405
+ }
406
+
407
+ // Margin shortcuts
408
+ if (converted.m !== undefined) {
409
+ converted.margin = spacingToPx(converted.m);
410
+ delete converted.m;
411
+ }
412
+ if (converted.mx !== undefined) {
413
+ converted.marginLeft = spacingToPx(converted.mx);
414
+ converted.marginRight = spacingToPx(converted.mx);
415
+ delete converted.mx;
416
+ }
417
+ if (converted.my !== undefined) {
418
+ converted.marginTop = spacingToPx(converted.my);
419
+ converted.marginBottom = spacingToPx(converted.my);
420
+ delete converted.my;
421
+ }
422
+ if (converted.mt !== undefined) {
423
+ converted.marginTop = spacingToPx(converted.mt);
424
+ delete converted.mt;
425
+ }
426
+ if (converted.mb !== undefined) {
427
+ converted.marginBottom = spacingToPx(converted.mb);
428
+ delete converted.mb;
429
+ }
430
+ if (converted.ml !== undefined) {
431
+ converted.marginLeft = spacingToPx(converted.ml);
432
+ delete converted.ml;
433
+ }
434
+ if (converted.mr !== undefined) {
435
+ converted.marginRight = spacingToPx(converted.mr);
436
+ delete converted.mr;
437
+ }
438
+
439
+ // Gap
440
+ if (converted.gap !== undefined) {
441
+ converted.gap = spacingToPx(converted.gap);
442
+ }
443
+
444
+ return converted;
445
+ }, [sx]);
446
+
447
+ // Extract padding from style to allow override
448
+ const {
449
+ padding,
450
+ paddingTop,
451
+ paddingBottom,
452
+ paddingLeft,
453
+ paddingRight,
454
+ backgroundColor,
455
+ ...restStyle
456
+ } = style || {};
457
+
458
+ // Check if padding is explicitly set (if padding is set, individual paddings should be undefined)
459
+ // If no padding is set at all, use defaults
460
+ const hasAnyPadding =
461
+ padding !== undefined ||
462
+ paddingTop !== undefined ||
463
+ paddingBottom !== undefined ||
464
+ paddingLeft !== undefined ||
465
+ paddingRight !== undefined;
466
+
467
+ return (
468
+ <h2
469
+ ref={ref}
470
+ className={cn(
471
+ "rounded-t-[inherit]",
472
+ "text-lg font-semibold leading-none tracking-tight",
473
+ "text-foreground",
474
+ // DialogTitle is typically used inside DialogHeader which has padding
475
+ // Only add padding if explicitly needed (when used standalone)
476
+ // For normal use, padding is handled by DialogHeader
477
+ className
478
+ )}
479
+ style={{
480
+ // Only set background if explicitly provided, otherwise inherit from parent
481
+ ...(backgroundColor !== undefined
482
+ ? backgroundColor.startsWith("linear-gradient") ||
483
+ backgroundColor.startsWith("radial-gradient") ||
484
+ backgroundColor.startsWith("conic-gradient")
485
+ ? { background: backgroundColor }
486
+ : { backgroundColor }
487
+ : {}),
488
+ // Allow padding override via style prop
489
+ padding: padding,
490
+ paddingLeft: paddingLeft,
491
+ paddingRight: paddingRight,
492
+ paddingTop: paddingTop,
493
+ paddingBottom: paddingBottom,
494
+ ...sxStyles,
495
+ ...restStyle,
496
+ }}
497
+ {...props}
498
+ />
499
+ );
500
+ }
501
+ );
502
+ DialogTitle.displayName = "DialogTitle";
503
+
504
+ const DialogDescription = React.forwardRef(
505
+ ({ className, id, ...props }, ref) => (
506
+ <p
507
+ ref={ref}
508
+ id={id}
509
+ className={cn("text-sm text-muted-foreground", className)}
510
+ {...props}
511
+ />
512
+ )
513
+ );
514
+ DialogDescription.displayName = "DialogDescription";
515
+
516
+ const DialogActions = ({ className, style, ...props }) => {
517
+ const actualMode = useIsDarkMode();
518
+
519
+ // Extract padding and gap from style to allow override
520
+ const {
521
+ padding,
522
+ paddingTop,
523
+ paddingBottom,
524
+ paddingLeft,
525
+ paddingRight,
526
+ gap,
527
+ backgroundColor,
528
+ ...restStyle
529
+ } = style || {};
530
+
531
+ return (
532
+ <div
533
+ className={cn(
534
+ "flex items-center justify-end rounded-b-[inherit]",
535
+ className
536
+ )}
537
+ style={{
538
+ // Only set background if explicitly provided, otherwise inherit from parent
539
+ backgroundColor: backgroundColor,
540
+ // If padding is set, let it control all sides; otherwise use individual defaults
541
+ padding: padding,
542
+ paddingLeft:
543
+ paddingLeft !== undefined
544
+ ? paddingLeft
545
+ : padding !== undefined
546
+ ? undefined
547
+ : "1rem",
548
+ paddingRight:
549
+ paddingRight !== undefined
550
+ ? paddingRight
551
+ : padding !== undefined
552
+ ? undefined
553
+ : "1rem",
554
+ paddingTop:
555
+ paddingTop !== undefined
556
+ ? paddingTop
557
+ : padding !== undefined
558
+ ? undefined
559
+ : "0.5rem",
560
+ paddingBottom:
561
+ paddingBottom !== undefined
562
+ ? paddingBottom
563
+ : padding !== undefined
564
+ ? undefined
565
+ : "1rem",
566
+ gap: gap !== undefined ? gap : "0.5rem",
567
+ opacity: 1,
568
+ ...restStyle,
569
+ }}
570
+ {...props}
571
+ />
572
+ );
573
+ };
574
+ DialogActions.displayName = "DialogActions";
575
+
576
+ export {
577
+ Dialog,
578
+ DialogContent,
579
+ DialogHeader,
580
+ DialogFooter,
581
+ DialogTitle,
582
+ DialogDescription,
583
+ DialogActions,
584
+ };