@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,347 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/utils";
3
+ import { TextField } from "./text-field";
4
+
5
+ const Autocomplete = React.forwardRef(
6
+ (
7
+ {
8
+ options = [],
9
+ getOptionLabel,
10
+ value,
11
+ onChange,
12
+ inputValue,
13
+ onInputChange,
14
+ filterOptions,
15
+ renderInput,
16
+ className,
17
+ style,
18
+ ...props
19
+ },
20
+ ref
21
+ ) => {
22
+ const [open, setOpen] = React.useState(false);
23
+ const [filteredOptions, setFilteredOptions] = React.useState(options);
24
+ const inputRef = React.useRef(null);
25
+ const listRef = React.useRef(null);
26
+
27
+ // Combine refs
28
+ React.useImperativeHandle(ref, () => inputRef.current);
29
+
30
+ // Filter options when inputValue or options change
31
+ React.useEffect(() => {
32
+ // Ensure inputValue is always a string
33
+ const searchValue = typeof inputValue === "string" ? inputValue : (inputValue ? String(inputValue) : "");
34
+
35
+ if (filterOptions) {
36
+ const filtered = filterOptions(options, {
37
+ inputValue: searchValue,
38
+ });
39
+ setFilteredOptions(filtered);
40
+ } else {
41
+ // Default filtering: filter options based on inputValue
42
+ if (searchValue) {
43
+ const filtered = options.filter((option) => {
44
+ const optionLabel = getOptionLabel
45
+ ? getOptionLabel(option)
46
+ : option?.name || option?.label || String(option);
47
+ return String(optionLabel).toLowerCase().includes(searchValue.toLowerCase());
48
+ });
49
+ setFilteredOptions(filtered);
50
+ } else {
51
+ // Show all options if input is empty
52
+ setFilteredOptions(options);
53
+ }
54
+ }
55
+ }, [options, inputValue, filterOptions, getOptionLabel]);
56
+
57
+ const handleInputChange = (event) => {
58
+ // Extract value from event, ensuring it's always a string
59
+ let newValue = "";
60
+ if (event?.target?.value !== undefined) {
61
+ newValue = String(event.target.value);
62
+ } else if (typeof event === "string") {
63
+ newValue = event;
64
+ } else if (event) {
65
+ newValue = String(event);
66
+ }
67
+
68
+ if (onInputChange) {
69
+ onInputChange(event, newValue);
70
+ }
71
+ if (!open) {
72
+ setOpen(true);
73
+ }
74
+ };
75
+
76
+ const handleOptionClick = (option) => {
77
+ if (onChange) {
78
+ onChange(null, option);
79
+ }
80
+ // Update inputValue when option is selected
81
+ const optionLabel = getOptionLabel
82
+ ? getOptionLabel(option)
83
+ : option?.name || option?.label || String(option);
84
+ // Ensure optionLabel is a string
85
+ const labelString = typeof optionLabel === "string" ? optionLabel : String(optionLabel || "");
86
+ if (onInputChange) {
87
+ onInputChange(null, labelString);
88
+ }
89
+ setOpen(false);
90
+ };
91
+
92
+ const handleClickOutside = (event) => {
93
+ if (
94
+ listRef.current &&
95
+ !listRef.current.contains(event.target) &&
96
+ inputRef.current &&
97
+ !inputRef.current.contains(event.target)
98
+ ) {
99
+ setOpen(false);
100
+ }
101
+ };
102
+
103
+ React.useEffect(() => {
104
+ if (open) {
105
+ document.addEventListener("mousedown", handleClickOutside);
106
+ return () => {
107
+ document.removeEventListener("mousedown", handleClickOutside);
108
+ };
109
+ }
110
+ }, [open]);
111
+
112
+ const displayValue = value
113
+ ? getOptionLabel
114
+ ? getOptionLabel(value)
115
+ : value?.name || value?.label || String(value)
116
+ : "";
117
+
118
+ // Determine the input value: use inputValue if provided, otherwise use displayValue
119
+ // When value is selected, show displayValue unless user is typing
120
+ const getInputValue = () => {
121
+ // Always ensure we return a string
122
+ if (inputValue !== undefined && inputValue !== null) {
123
+ // If user is typing, show inputValue (ensure it's a string)
124
+ return typeof inputValue === "string" ? inputValue : String(inputValue);
125
+ }
126
+ // If value is selected, show displayValue
127
+ return typeof displayValue === "string" ? displayValue : String(displayValue || "");
128
+ };
129
+
130
+ const inputProps = {
131
+ ref: inputRef,
132
+ value: getInputValue(),
133
+ onChange: handleInputChange,
134
+ onFocus: () => setOpen(true),
135
+ ...props,
136
+ };
137
+
138
+ // If renderInput is provided, use it (for MUI compatibility)
139
+ if (renderInput) {
140
+ return (
141
+ <div ref={ref} className={cn("relative", className)} style={style}>
142
+ {renderInput({
143
+ ...inputProps,
144
+ InputProps: {
145
+ ...(inputProps.InputProps || {}),
146
+ endAdornment: (
147
+ <div
148
+ className="absolute right-2 top-1/2 -translate-y-1/2 cursor-pointer"
149
+ onClick={(e) => {
150
+ e.stopPropagation();
151
+ e.preventDefault();
152
+ setOpen(!open);
153
+ }}
154
+ onMouseDown={(e) => {
155
+ e.stopPropagation();
156
+ e.preventDefault();
157
+ }}
158
+ >
159
+ <svg
160
+ width="20"
161
+ height="20"
162
+ viewBox="0 0 20 20"
163
+ fill="none"
164
+ style={{
165
+ transform: open ? "rotate(180deg)" : "rotate(0deg)",
166
+ transition: "transform 0.2s",
167
+ }}
168
+ >
169
+ <path
170
+ d="M5 7.5L10 12.5L15 7.5"
171
+ stroke="hsl(var(--muted-foreground))"
172
+ strokeWidth="2"
173
+ strokeLinecap="round"
174
+ strokeLinejoin="round"
175
+ />
176
+ </svg>
177
+ </div>
178
+ ),
179
+ },
180
+ })}
181
+ {open && filteredOptions.length > 0 && (
182
+ <div
183
+ ref={listRef}
184
+ className="absolute z-50 w-full mt-1 rounded-2xl border shadow-lg overflow-auto"
185
+ style={{
186
+ maxHeight: "200px", // Ensure at least 4 options are visible (each ~50px)
187
+ minHeight: "160px", // Minimum height for better UX
188
+ backgroundColor: "hsl(var(--card))",
189
+ borderColor: "hsl(var(--border))",
190
+ top: "100%",
191
+ }}
192
+ >
193
+ {filteredOptions.map((option, index) => {
194
+ const optionLabel = getOptionLabel
195
+ ? getOptionLabel(option)
196
+ : option?.name || option?.label || String(option);
197
+ const isSelected =
198
+ value &&
199
+ (getOptionLabel
200
+ ? getOptionLabel(value) === optionLabel
201
+ : value === option ||
202
+ (value?.id && option?.id && value.id === option.id));
203
+
204
+ return (
205
+ <div
206
+ key={index}
207
+ className="px-3 py-2 cursor-pointer hover:bg-accent transition-colors"
208
+ style={{
209
+ backgroundColor: isSelected
210
+ ? "hsl(var(--accent))"
211
+ : "transparent",
212
+ color: "hsl(var(--foreground))",
213
+ }}
214
+ onClick={() => handleOptionClick(option)}
215
+ onMouseEnter={(e) => {
216
+ if (!isSelected) {
217
+ e.currentTarget.style.backgroundColor =
218
+ "hsl(var(--accent))";
219
+ }
220
+ }}
221
+ onMouseLeave={(e) => {
222
+ e.currentTarget.style.backgroundColor = isSelected
223
+ ? "hsl(var(--accent))"
224
+ : "transparent";
225
+ }}
226
+ >
227
+ {optionLabel}
228
+ </div>
229
+ );
230
+ })}
231
+ </div>
232
+ )}
233
+ </div>
234
+ );
235
+ }
236
+
237
+ // Default render with TextField
238
+ return (
239
+ <div ref={ref} className={cn("relative", className)} style={style}>
240
+ <TextField
241
+ {...inputProps}
242
+ style={{
243
+ ...inputProps.style,
244
+ height: inputProps.style?.height || "40px",
245
+ }}
246
+ InputProps={{
247
+ ...inputProps.InputProps,
248
+ style: {
249
+ ...inputProps.InputProps?.style,
250
+ backgroundColor:
251
+ inputProps.InputProps?.style?.backgroundColor || "transparent",
252
+ height: inputProps.InputProps?.style?.height || "40px",
253
+ },
254
+ endAdornment: (
255
+ <div
256
+ className="absolute right-2 top-1/2 -translate-y-1/2 cursor-pointer"
257
+ onClick={(e) => {
258
+ e.stopPropagation();
259
+ e.preventDefault();
260
+ setOpen(!open);
261
+ }}
262
+ onMouseDown={(e) => {
263
+ e.stopPropagation();
264
+ e.preventDefault();
265
+ }}
266
+ >
267
+ <svg
268
+ width="20"
269
+ height="20"
270
+ viewBox="0 0 20 20"
271
+ fill="none"
272
+ style={{
273
+ transform: open ? "rotate(180deg)" : "rotate(0deg)",
274
+ transition: "transform 0.2s",
275
+ }}
276
+ >
277
+ <path
278
+ d="M5 7.5L10 12.5L15 7.5"
279
+ stroke="hsl(var(--muted-foreground))"
280
+ strokeWidth="2"
281
+ strokeLinecap="round"
282
+ strokeLinejoin="round"
283
+ />
284
+ </svg>
285
+ </div>
286
+ ),
287
+ }}
288
+ />
289
+ {open && filteredOptions.length > 0 && (
290
+ <div
291
+ ref={listRef}
292
+ className="absolute z-50 w-full mt-1 rounded-2xl border shadow-lg overflow-auto"
293
+ style={{
294
+ maxHeight: "200px", // Ensure at least 4 options are visible (each ~50px)
295
+ minHeight: "160px", // Minimum height for better UX
296
+ backgroundColor: "hsl(var(--card))",
297
+ borderColor: "hsl(var(--border))",
298
+ top: "100%",
299
+ }}
300
+ >
301
+ {filteredOptions.map((option, index) => {
302
+ const optionLabel = getOptionLabel
303
+ ? getOptionLabel(option)
304
+ : option?.name || option?.label || String(option);
305
+ const isSelected =
306
+ value &&
307
+ (getOptionLabel
308
+ ? getOptionLabel(value) === optionLabel
309
+ : value === option ||
310
+ (value?.id && option?.id && value.id === option.id));
311
+
312
+ return (
313
+ <div
314
+ key={index}
315
+ className="px-3 py-2 cursor-pointer hover:bg-accent transition-colors"
316
+ style={{
317
+ backgroundColor: isSelected
318
+ ? "hsl(var(--accent))"
319
+ : "transparent",
320
+ color: "hsl(var(--foreground))",
321
+ }}
322
+ onClick={() => handleOptionClick(option)}
323
+ onMouseEnter={(e) => {
324
+ if (!isSelected) {
325
+ e.currentTarget.style.backgroundColor =
326
+ "hsl(var(--accent))";
327
+ }
328
+ }}
329
+ onMouseLeave={(e) => {
330
+ e.currentTarget.style.backgroundColor = isSelected
331
+ ? "hsl(var(--accent))"
332
+ : "transparent";
333
+ }}
334
+ >
335
+ {optionLabel}
336
+ </div>
337
+ );
338
+ })}
339
+ </div>
340
+ )}
341
+ </div>
342
+ );
343
+ }
344
+ );
345
+ Autocomplete.displayName = "Autocomplete";
346
+
347
+ export { Autocomplete };
@@ -0,0 +1,160 @@
1
+ import * as React from "react";
2
+ import { cn, useIsDarkMode } from "../lib/utils";
3
+
4
+ const Avatar = React.forwardRef(
5
+ (
6
+ {
7
+ className,
8
+ style,
9
+ src,
10
+ alt,
11
+ children,
12
+ variant = "circular",
13
+ sizes,
14
+ imgProps,
15
+ ...props
16
+ },
17
+ ref
18
+ ) => {
19
+ // Get variant styles
20
+ const getVariantStyles = () => {
21
+ if (variant === "rounded") {
22
+ return { borderRadius: "8px" };
23
+ }
24
+ if (variant === "square") {
25
+ return { borderRadius: "0px" };
26
+ }
27
+ // circular (default)
28
+ return { borderRadius: "50%" };
29
+ };
30
+
31
+ const defaultStyles = {
32
+ display: "inline-flex",
33
+ alignItems: "center",
34
+ justifyContent: "center",
35
+ flexShrink: 0,
36
+ position: "relative",
37
+ textAlign: "center",
38
+ fontSize: "1.25rem",
39
+ lineHeight: 1,
40
+ overflow: "hidden",
41
+ userSelect: "none",
42
+ backgroundColor: "hsl(var(--muted))",
43
+ color: "hsl(var(--foreground))",
44
+ fontWeight: 500,
45
+ width: "40px",
46
+ height: "40px",
47
+ ...getVariantStyles(),
48
+ };
49
+
50
+ const actualMode = useIsDarkMode();
51
+ const isDark = actualMode === "dark";
52
+
53
+ // If has src, render as image
54
+ if (src) {
55
+ // Get text color based on theme mode if backgroundColor is set but color is not
56
+ const getTextColor = () => {
57
+ // If color is explicitly set in style, use it
58
+ if (style?.color) {
59
+ return style.color;
60
+ }
61
+ // If backgroundColor is set in style, determine text color based on dark mode
62
+ if (style?.backgroundColor) {
63
+ return isDark ? "#000000" : "#ffffff";
64
+ }
65
+ // Otherwise use default color
66
+ return defaultStyles.color;
67
+ };
68
+
69
+ // Merge styles: defaultStyles first, then style to allow overrides
70
+ // But preserve borderRadius and backgroundColor from defaultStyles if not explicitly set in style
71
+ // IMPORTANT: color must be set last to ensure it's not overridden by style spread
72
+ const { color: styleColor, ...styleWithoutColor } = style || {};
73
+ const mergedStylesWithSrc = {
74
+ ...defaultStyles,
75
+ ...styleWithoutColor, // style without color to avoid override
76
+ // Ensure borderRadius is preserved if not explicitly set in style
77
+ borderRadius: style?.borderRadius ?? defaultStyles.borderRadius,
78
+ // Ensure backgroundColor is preserved if not explicitly set in style
79
+ backgroundColor:
80
+ style?.backgroundColor ?? defaultStyles.backgroundColor,
81
+ // Get text color based on theme mode (set last to ensure it's not overridden)
82
+ color: getTextColor(),
83
+ };
84
+
85
+ return (
86
+ <div
87
+ ref={ref}
88
+ className={cn("relative", className)}
89
+ style={mergedStylesWithSrc}
90
+ {...props}
91
+ >
92
+ <img
93
+ src={src}
94
+ alt={alt}
95
+ sizes={sizes}
96
+ style={{
97
+ width: "100%",
98
+ height: "100%",
99
+ objectFit: "cover",
100
+ }}
101
+ {...imgProps}
102
+ />
103
+ {children && (
104
+ <div
105
+ style={{
106
+ position: "absolute",
107
+ inset: 0,
108
+ display: "flex",
109
+ alignItems: "center",
110
+ justifyContent: "center",
111
+ }}
112
+ >
113
+ {children}
114
+ </div>
115
+ )}
116
+ </div>
117
+ );
118
+ }
119
+
120
+ // Render as div with children
121
+ // Get text color based on theme mode if backgroundColor is set but color is not
122
+ const getTextColor = () => {
123
+ // If color is explicitly set in style, use it
124
+ if (style?.color) {
125
+ return style.color;
126
+ }
127
+ // If backgroundColor is set in style, determine text color based on dark mode
128
+ if (style?.backgroundColor) {
129
+ return isDark ? "#000000" : "#ffffff";
130
+ }
131
+ // Otherwise use default color
132
+ return defaultStyles.color;
133
+ };
134
+
135
+ // Merge styles: defaultStyles first, then style to allow overrides
136
+ // But preserve borderRadius and backgroundColor from defaultStyles if not explicitly set in style
137
+ // IMPORTANT: color must be set last to ensure it's not overridden by style spread
138
+ const { color: styleColor, ...styleWithoutColor } = style || {};
139
+ const mergedStyles = {
140
+ ...defaultStyles,
141
+ ...styleWithoutColor, // style without color to avoid override
142
+ // Ensure borderRadius is preserved if not explicitly set in style
143
+ borderRadius: style?.borderRadius ?? defaultStyles.borderRadius,
144
+ // Ensure backgroundColor is preserved if not explicitly set in style
145
+ backgroundColor: style?.backgroundColor ?? defaultStyles.backgroundColor,
146
+ // Get text color based on theme mode (set last to ensure it's not overridden)
147
+ color: getTextColor(),
148
+ };
149
+
150
+ return (
151
+ <div ref={ref} className={cn(className)} style={mergedStyles} {...props}>
152
+ {children}
153
+ </div>
154
+ );
155
+ }
156
+ );
157
+
158
+ Avatar.displayName = "Avatar";
159
+
160
+ export { Avatar };
@@ -0,0 +1,165 @@
1
+ import * as React from "react";
2
+ import { cn, spacingToPx } from "../lib/utils";
3
+
4
+ const Box = React.forwardRef(
5
+ ({ className, sx, style, children, component = "div", ...props }, ref) => {
6
+ const Component = component;
7
+
8
+ // Convert sx prop to style if provided
9
+ const sxStyles = React.useMemo(() => {
10
+ if (!sx) return {};
11
+ const sxObj = typeof sx === "function" ? sx({}) : sx;
12
+ const converted = { ...sxObj };
13
+
14
+ // Handle spacing shortcuts
15
+ if (converted.p !== undefined) {
16
+ converted.padding = spacingToPx(converted.p);
17
+ delete converted.p;
18
+ }
19
+ if (converted.px !== undefined) {
20
+ converted.paddingLeft = spacingToPx(converted.px);
21
+ converted.paddingRight = spacingToPx(converted.px);
22
+ delete converted.px;
23
+ }
24
+ if (converted.py !== undefined) {
25
+ converted.paddingTop = spacingToPx(converted.py);
26
+ converted.paddingBottom = spacingToPx(converted.py);
27
+ delete converted.py;
28
+ }
29
+ if (converted.pt !== undefined) {
30
+ converted.paddingTop = spacingToPx(converted.pt);
31
+ delete converted.pt;
32
+ }
33
+ if (converted.pb !== undefined) {
34
+ converted.paddingBottom = spacingToPx(converted.pb);
35
+ delete converted.pb;
36
+ }
37
+ if (converted.pl !== undefined) {
38
+ converted.paddingLeft = spacingToPx(converted.pl);
39
+ delete converted.pl;
40
+ }
41
+ if (converted.pr !== undefined) {
42
+ converted.paddingRight = spacingToPx(converted.pr);
43
+ delete converted.pr;
44
+ }
45
+ if (converted.m !== undefined) {
46
+ converted.margin = spacingToPx(converted.m);
47
+ delete converted.m;
48
+ }
49
+ if (converted.mx !== undefined) {
50
+ converted.marginLeft = spacingToPx(converted.mx);
51
+ converted.marginRight = spacingToPx(converted.mx);
52
+ delete converted.mx;
53
+ }
54
+ if (converted.my !== undefined) {
55
+ converted.marginTop = spacingToPx(converted.my);
56
+ converted.marginBottom = spacingToPx(converted.my);
57
+ delete converted.my;
58
+ }
59
+ if (converted.mt !== undefined) {
60
+ converted.marginTop = spacingToPx(converted.mt);
61
+ delete converted.mt;
62
+ }
63
+ if (converted.mb !== undefined) {
64
+ converted.marginBottom = spacingToPx(converted.mb);
65
+ delete converted.mb;
66
+ }
67
+ if (converted.ml !== undefined) {
68
+ converted.marginLeft = spacingToPx(converted.ml);
69
+ delete converted.ml;
70
+ }
71
+ if (converted.mr !== undefined) {
72
+ converted.marginRight = spacingToPx(converted.mr);
73
+ delete converted.mr;
74
+ }
75
+ if (converted.gap !== undefined) {
76
+ converted.gap = spacingToPx(converted.gap);
77
+ delete converted.gap;
78
+ }
79
+
80
+ // Handle color shortcuts (text.primary, text.secondary, etc.)
81
+ if (converted.color) {
82
+ if (typeof converted.color === "string") {
83
+ if (
84
+ converted.color === "text.primary" ||
85
+ converted.color === "textPrimary"
86
+ ) {
87
+ converted.color = "hsl(var(--foreground))";
88
+ } else if (
89
+ converted.color === "text.secondary" ||
90
+ converted.color === "textSecondary"
91
+ ) {
92
+ converted.color = "hsl(var(--muted-foreground))";
93
+ } else if (converted.color === "primary") {
94
+ converted.color = "hsl(var(--primary))";
95
+ } else if (converted.color === "secondary") {
96
+ converted.color = "hsl(var(--secondary))";
97
+ } else if (converted.color === "error") {
98
+ converted.color = "hsl(var(--destructive))";
99
+ }
100
+ }
101
+ }
102
+
103
+ // Handle bgcolor - convert to CSS variable if needed
104
+ if (converted.bgcolor) {
105
+ if (
106
+ typeof converted.bgcolor === "string" &&
107
+ converted.bgcolor.includes(".")
108
+ ) {
109
+ // Map common theme paths to CSS variables
110
+ const bgcolorMap = {
111
+ "background.paper": "hsl(var(--card))",
112
+ "background.default": "hsl(var(--background))",
113
+ "background.card": "hsl(var(--card))",
114
+ };
115
+ converted.backgroundColor =
116
+ bgcolorMap[converted.bgcolor] || converted.bgcolor;
117
+ } else {
118
+ converted.backgroundColor = converted.bgcolor;
119
+ }
120
+ delete converted.bgcolor;
121
+ }
122
+
123
+ return converted;
124
+ }, [sx]);
125
+
126
+ const mergedStyle = {
127
+ ...sxStyles,
128
+ ...style,
129
+ };
130
+
131
+ // Convert display, flexDirection, etc. to Tailwind classes where possible
132
+ const displayClass = sxStyles.display === "flex" ? "flex" : "";
133
+ const flexDirectionClass =
134
+ sxStyles.flexDirection === "column" ? "flex-col" : "";
135
+ const alignItemsClass =
136
+ sxStyles.alignItems === "center" ? "items-center" : "";
137
+ const justifyContentClass =
138
+ sxStyles.justifyContent === "space-between"
139
+ ? "justify-between"
140
+ : sxStyles.justifyContent === "center"
141
+ ? "justify-center"
142
+ : "";
143
+
144
+ return (
145
+ <Component
146
+ ref={ref}
147
+ className={cn(
148
+ displayClass,
149
+ flexDirectionClass,
150
+ alignItemsClass,
151
+ justifyContentClass,
152
+ className
153
+ )}
154
+ style={mergedStyle}
155
+ {...props}
156
+ >
157
+ {children}
158
+ </Component>
159
+ );
160
+ }
161
+ );
162
+
163
+ Box.displayName = "Box";
164
+
165
+ export { Box };