@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.
- package/README.md +188 -0
- package/dist/index.cjs.js +9 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.css +2 -0
- package/dist/index.esm.js +9 -0
- package/dist/index.esm.js.map +1 -0
- package/package.json +54 -0
- package/src/common/adaptive-theme-provider.js +19 -0
- package/src/components/accordion.jsx +306 -0
- package/src/components/alert.jsx +137 -0
- package/src/components/app-bar.jsx +105 -0
- package/src/components/autocomplete.jsx +347 -0
- package/src/components/avatar.jsx +160 -0
- package/src/components/box.jsx +165 -0
- package/src/components/button.jsx +104 -0
- package/src/components/card.jsx +156 -0
- package/src/components/checkbox.jsx +63 -0
- package/src/components/chip.jsx +137 -0
- package/src/components/collapse.jsx +188 -0
- package/src/components/container.jsx +67 -0
- package/src/components/date-picker.jsx +528 -0
- package/src/components/dialog-content-text.jsx +27 -0
- package/src/components/dialog.jsx +584 -0
- package/src/components/divider.jsx +192 -0
- package/src/components/drawer.jsx +255 -0
- package/src/components/form-control-label.jsx +89 -0
- package/src/components/form-group.jsx +32 -0
- package/src/components/form-label.jsx +54 -0
- package/src/components/grid.jsx +135 -0
- package/src/components/icon-button.jsx +101 -0
- package/src/components/index.js +78 -0
- package/src/components/input-adornment.jsx +43 -0
- package/src/components/input-label.jsx +55 -0
- package/src/components/list.jsx +239 -0
- package/src/components/menu.jsx +370 -0
- package/src/components/paper.jsx +173 -0
- package/src/components/radio-group.jsx +76 -0
- package/src/components/radio.jsx +108 -0
- package/src/components/select.jsx +308 -0
- package/src/components/slider.jsx +382 -0
- package/src/components/stack.jsx +110 -0
- package/src/components/table.jsx +243 -0
- package/src/components/tabs.jsx +363 -0
- package/src/components/text-field.jsx +289 -0
- package/src/components/toggle-button.jsx +209 -0
- package/src/components/toolbar.jsx +48 -0
- package/src/components/tooltip.jsx +127 -0
- package/src/components/typography.jsx +77 -0
- package/src/global-state.js +29 -0
- package/src/index.css +110 -0
- package/src/index.js +6 -0
- package/src/lib/useMediaQuery.js +37 -0
- package/src/lib/utils.js +113 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "../lib/utils";
|
|
3
|
+
|
|
4
|
+
const TableContext = React.createContext({
|
|
5
|
+
verticalDivider: false,
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const Table = React.forwardRef(
|
|
9
|
+
(
|
|
10
|
+
{
|
|
11
|
+
className,
|
|
12
|
+
size = "medium",
|
|
13
|
+
sx,
|
|
14
|
+
style,
|
|
15
|
+
border = false,
|
|
16
|
+
verticalDivider = false,
|
|
17
|
+
children,
|
|
18
|
+
...props
|
|
19
|
+
},
|
|
20
|
+
ref
|
|
21
|
+
) => {
|
|
22
|
+
const mergedSx = React.useMemo(() => {
|
|
23
|
+
if (!sx) return {};
|
|
24
|
+
return typeof sx === "function" ? sx({}) : sx;
|
|
25
|
+
}, [sx]);
|
|
26
|
+
|
|
27
|
+
const sizeStyles = {
|
|
28
|
+
small: {
|
|
29
|
+
fontSize: "0.875rem",
|
|
30
|
+
},
|
|
31
|
+
medium: {
|
|
32
|
+
fontSize: "1rem",
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<TableContext.Provider value={{ verticalDivider }}>
|
|
38
|
+
<table
|
|
39
|
+
ref={ref}
|
|
40
|
+
className={cn("w-full", className)}
|
|
41
|
+
style={{
|
|
42
|
+
borderCollapse: "separate",
|
|
43
|
+
borderSpacing: 0,
|
|
44
|
+
...(border && {
|
|
45
|
+
border: "1px solid hsl(var(--border))",
|
|
46
|
+
}),
|
|
47
|
+
...sizeStyles[size],
|
|
48
|
+
...mergedSx,
|
|
49
|
+
...style,
|
|
50
|
+
}}
|
|
51
|
+
{...props}
|
|
52
|
+
>
|
|
53
|
+
{children}
|
|
54
|
+
</table>
|
|
55
|
+
</TableContext.Provider>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
Table.displayName = "Table";
|
|
60
|
+
|
|
61
|
+
const TableContainer = React.forwardRef(
|
|
62
|
+
({ className, component, sx, style, children, ...props }, ref) => {
|
|
63
|
+
const mergedSx = React.useMemo(() => {
|
|
64
|
+
if (!sx) return {};
|
|
65
|
+
return typeof sx === "function" ? sx({}) : sx;
|
|
66
|
+
}, [sx]);
|
|
67
|
+
|
|
68
|
+
const Component = component || "div";
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<Component
|
|
72
|
+
ref={ref}
|
|
73
|
+
className={cn("relative", className)}
|
|
74
|
+
style={{
|
|
75
|
+
...mergedSx,
|
|
76
|
+
...style,
|
|
77
|
+
}}
|
|
78
|
+
{...props}
|
|
79
|
+
>
|
|
80
|
+
{children}
|
|
81
|
+
</Component>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
TableContainer.displayName = "TableContainer";
|
|
86
|
+
|
|
87
|
+
const TableHead = React.forwardRef(
|
|
88
|
+
({ className, sx, style, children, ...props }, ref) => {
|
|
89
|
+
const mergedSx = React.useMemo(() => {
|
|
90
|
+
if (!sx) return {};
|
|
91
|
+
return typeof sx === "function" ? sx({}) : sx;
|
|
92
|
+
}, [sx]);
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<thead
|
|
96
|
+
ref={ref}
|
|
97
|
+
className={cn(className)}
|
|
98
|
+
style={{
|
|
99
|
+
...mergedSx,
|
|
100
|
+
...style,
|
|
101
|
+
}}
|
|
102
|
+
{...props}
|
|
103
|
+
>
|
|
104
|
+
{children}
|
|
105
|
+
</thead>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
TableHead.displayName = "TableHead";
|
|
110
|
+
|
|
111
|
+
const TableBody = React.forwardRef(
|
|
112
|
+
({ className, sx, style, children, ...props }, ref) => {
|
|
113
|
+
const mergedSx = React.useMemo(() => {
|
|
114
|
+
if (!sx) return {};
|
|
115
|
+
return typeof sx === "function" ? sx({}) : sx;
|
|
116
|
+
}, [sx]);
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<tbody
|
|
120
|
+
ref={ref}
|
|
121
|
+
className={cn(className)}
|
|
122
|
+
style={{
|
|
123
|
+
...mergedSx,
|
|
124
|
+
...style,
|
|
125
|
+
}}
|
|
126
|
+
{...props}
|
|
127
|
+
>
|
|
128
|
+
{children}
|
|
129
|
+
</tbody>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
TableBody.displayName = "TableBody";
|
|
134
|
+
|
|
135
|
+
const TableRow = React.forwardRef(
|
|
136
|
+
({ className, sx, style, children, hover, selected, ...props }, ref) => {
|
|
137
|
+
const mergedSx = React.useMemo(() => {
|
|
138
|
+
if (!sx) return {};
|
|
139
|
+
return typeof sx === "function" ? sx({}) : sx;
|
|
140
|
+
}, [sx]);
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<tr
|
|
144
|
+
ref={ref}
|
|
145
|
+
className={cn(
|
|
146
|
+
hover && "hover:bg-accent",
|
|
147
|
+
selected && "bg-accent",
|
|
148
|
+
className
|
|
149
|
+
)}
|
|
150
|
+
style={{
|
|
151
|
+
...mergedSx,
|
|
152
|
+
...style,
|
|
153
|
+
}}
|
|
154
|
+
{...props}
|
|
155
|
+
>
|
|
156
|
+
{children}
|
|
157
|
+
</tr>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
TableRow.displayName = "TableRow";
|
|
162
|
+
|
|
163
|
+
const TableCell = React.forwardRef(
|
|
164
|
+
(
|
|
165
|
+
{
|
|
166
|
+
className,
|
|
167
|
+
align = "inherit",
|
|
168
|
+
padding = "normal",
|
|
169
|
+
size = "medium",
|
|
170
|
+
scope,
|
|
171
|
+
sx,
|
|
172
|
+
style,
|
|
173
|
+
children,
|
|
174
|
+
colSpan,
|
|
175
|
+
rowSpan,
|
|
176
|
+
...props
|
|
177
|
+
},
|
|
178
|
+
ref
|
|
179
|
+
) => {
|
|
180
|
+
const mergedSx = React.useMemo(() => {
|
|
181
|
+
if (!sx) return {};
|
|
182
|
+
return typeof sx === "function" ? sx({}) : sx;
|
|
183
|
+
}, [sx]);
|
|
184
|
+
|
|
185
|
+
const paddingMap = {
|
|
186
|
+
none: "0",
|
|
187
|
+
checkbox: "0 0 0 4px",
|
|
188
|
+
normal: "16px",
|
|
189
|
+
small: "8px",
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const sizeStyles = {
|
|
193
|
+
small: {
|
|
194
|
+
padding: "6px 8px",
|
|
195
|
+
fontSize: "0.875rem",
|
|
196
|
+
},
|
|
197
|
+
medium: {
|
|
198
|
+
padding: paddingMap[padding] || paddingMap.normal,
|
|
199
|
+
fontSize: "1rem",
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const alignStyles = {
|
|
204
|
+
left: { textAlign: "left" },
|
|
205
|
+
center: { textAlign: "center" },
|
|
206
|
+
right: { textAlign: "right" },
|
|
207
|
+
inherit: {},
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const { verticalDivider } = React.useContext(TableContext);
|
|
211
|
+
|
|
212
|
+
const Component = scope ? "th" : "td";
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
<Component
|
|
216
|
+
ref={ref}
|
|
217
|
+
scope={scope}
|
|
218
|
+
colSpan={colSpan}
|
|
219
|
+
rowSpan={rowSpan}
|
|
220
|
+
className={cn(
|
|
221
|
+
"border-b border-border",
|
|
222
|
+
verticalDivider && "border-r border-border last:border-r-0",
|
|
223
|
+
className
|
|
224
|
+
)}
|
|
225
|
+
style={{
|
|
226
|
+
...sizeStyles[size],
|
|
227
|
+
...alignStyles[align],
|
|
228
|
+
...(verticalDivider && {
|
|
229
|
+
borderRight: "1px solid hsl(var(--border))",
|
|
230
|
+
}),
|
|
231
|
+
...mergedSx,
|
|
232
|
+
...style,
|
|
233
|
+
}}
|
|
234
|
+
{...props}
|
|
235
|
+
>
|
|
236
|
+
{children}
|
|
237
|
+
</Component>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
);
|
|
241
|
+
TableCell.displayName = "TableCell";
|
|
242
|
+
|
|
243
|
+
export { Table, TableContainer, TableHead, TableBody, TableRow, TableCell };
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "../lib/utils";
|
|
3
|
+
|
|
4
|
+
const Tabs = React.forwardRef(
|
|
5
|
+
(
|
|
6
|
+
{
|
|
7
|
+
className,
|
|
8
|
+
value,
|
|
9
|
+
onChange,
|
|
10
|
+
children,
|
|
11
|
+
centered,
|
|
12
|
+
variant, // "standard" for underline style, undefined/"pills"/"button" for circle button style
|
|
13
|
+
style,
|
|
14
|
+
...props
|
|
15
|
+
},
|
|
16
|
+
ref
|
|
17
|
+
) => {
|
|
18
|
+
const tabsContainerRef = React.useRef(null);
|
|
19
|
+
const indicatorRef = React.useRef(null);
|
|
20
|
+
const [selectedValue, setSelectedValue] = React.useState(() => {
|
|
21
|
+
return value !== undefined ? value : 0;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Sync internal state with value prop
|
|
25
|
+
React.useEffect(() => {
|
|
26
|
+
if (value !== undefined) {
|
|
27
|
+
setSelectedValue(value);
|
|
28
|
+
}
|
|
29
|
+
}, [value]);
|
|
30
|
+
|
|
31
|
+
const handleTabClick = React.useCallback(
|
|
32
|
+
(index) => {
|
|
33
|
+
const newValue = index;
|
|
34
|
+
if (value === undefined) {
|
|
35
|
+
setSelectedValue(newValue);
|
|
36
|
+
}
|
|
37
|
+
if (onChange) {
|
|
38
|
+
onChange(null, newValue);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
[value, onChange]
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const currentValue = value !== undefined ? value : selectedValue;
|
|
45
|
+
const isStandardVariant = variant === "standard";
|
|
46
|
+
|
|
47
|
+
// Update indicator position when selected tab changes (only for standard variant)
|
|
48
|
+
React.useEffect(() => {
|
|
49
|
+
if (
|
|
50
|
+
!isStandardVariant ||
|
|
51
|
+
!tabsContainerRef.current ||
|
|
52
|
+
!indicatorRef.current
|
|
53
|
+
)
|
|
54
|
+
return;
|
|
55
|
+
|
|
56
|
+
const tabs =
|
|
57
|
+
tabsContainerRef.current.querySelectorAll("[data-tab-index]");
|
|
58
|
+
const selectedTab = tabs[currentValue];
|
|
59
|
+
|
|
60
|
+
if (selectedTab) {
|
|
61
|
+
const containerRect = tabsContainerRef.current.getBoundingClientRect();
|
|
62
|
+
const tabRect = selectedTab.getBoundingClientRect();
|
|
63
|
+
|
|
64
|
+
indicatorRef.current.style.left = `${
|
|
65
|
+
tabRect.left - containerRect.left
|
|
66
|
+
}px`;
|
|
67
|
+
indicatorRef.current.style.width = `${tabRect.width}px`;
|
|
68
|
+
}
|
|
69
|
+
}, [currentValue, children, isStandardVariant]);
|
|
70
|
+
|
|
71
|
+
const tabsId = React.useId();
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div
|
|
75
|
+
ref={ref}
|
|
76
|
+
className={cn(
|
|
77
|
+
isStandardVariant ? "relative mb-4" : "flex",
|
|
78
|
+
!isStandardVariant && centered && "justify-center",
|
|
79
|
+
className
|
|
80
|
+
)}
|
|
81
|
+
style={{
|
|
82
|
+
minHeight: "auto",
|
|
83
|
+
...style,
|
|
84
|
+
}}
|
|
85
|
+
{...props}
|
|
86
|
+
>
|
|
87
|
+
<div
|
|
88
|
+
ref={tabsContainerRef}
|
|
89
|
+
role="tablist"
|
|
90
|
+
aria-label="Tabs"
|
|
91
|
+
className={cn(
|
|
92
|
+
isStandardVariant
|
|
93
|
+
? "flex border-b relative w-full"
|
|
94
|
+
: "flex gap-4 px-8 py-4"
|
|
95
|
+
)}
|
|
96
|
+
style={{
|
|
97
|
+
...(isStandardVariant && { borderColor: "hsl(var(--border))" }),
|
|
98
|
+
...(isStandardVariant && { display: "flex", width: "100%" }),
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
{React.Children.map(children, (child, index) => {
|
|
102
|
+
// Check if child is a Tab component by checking displayName or type
|
|
103
|
+
const isTabComponent =
|
|
104
|
+
React.isValidElement(child) &&
|
|
105
|
+
(child.type === Tab || child.type?.displayName === "Tab");
|
|
106
|
+
|
|
107
|
+
if (isTabComponent) {
|
|
108
|
+
// Ensure both values are numbers for comparison
|
|
109
|
+
const isSelected = Number(currentValue) === Number(index);
|
|
110
|
+
return React.cloneElement(child, {
|
|
111
|
+
...child.props,
|
|
112
|
+
selected: isSelected,
|
|
113
|
+
onClick: () => handleTabClick(index),
|
|
114
|
+
index: index,
|
|
115
|
+
"data-tab-index": index,
|
|
116
|
+
variant: isStandardVariant ? "standard" : undefined,
|
|
117
|
+
id: `${tabsId}-tab-${index}`,
|
|
118
|
+
"aria-controls": `${tabsId}-panel-${index}`,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return child;
|
|
122
|
+
})}
|
|
123
|
+
</div>
|
|
124
|
+
{/* Indicator line - only for standard variant */}
|
|
125
|
+
{isStandardVariant && (
|
|
126
|
+
<div
|
|
127
|
+
ref={indicatorRef}
|
|
128
|
+
style={{
|
|
129
|
+
position: "absolute",
|
|
130
|
+
bottom: 0,
|
|
131
|
+
height: "2px",
|
|
132
|
+
backgroundColor: "hsl(var(--primary))",
|
|
133
|
+
transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
|
|
134
|
+
}}
|
|
135
|
+
/>
|
|
136
|
+
)}
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
Tabs.displayName = "Tabs";
|
|
142
|
+
|
|
143
|
+
const Tab = React.forwardRef(
|
|
144
|
+
(
|
|
145
|
+
{
|
|
146
|
+
className,
|
|
147
|
+
label,
|
|
148
|
+
selected,
|
|
149
|
+
onClick,
|
|
150
|
+
index,
|
|
151
|
+
style,
|
|
152
|
+
variant,
|
|
153
|
+
id,
|
|
154
|
+
"aria-controls": ariaControls,
|
|
155
|
+
...props
|
|
156
|
+
},
|
|
157
|
+
ref
|
|
158
|
+
) => {
|
|
159
|
+
const isStandardVariant = variant === "standard";
|
|
160
|
+
|
|
161
|
+
const {
|
|
162
|
+
padding,
|
|
163
|
+
paddingLeft,
|
|
164
|
+
paddingRight,
|
|
165
|
+
paddingTop,
|
|
166
|
+
paddingBottom,
|
|
167
|
+
margin,
|
|
168
|
+
marginLeft,
|
|
169
|
+
marginRight,
|
|
170
|
+
backgroundColor: styleBackgroundColor,
|
|
171
|
+
color: styleColor,
|
|
172
|
+
...restStyle
|
|
173
|
+
} = style || {};
|
|
174
|
+
|
|
175
|
+
const hasPaddingInStyle =
|
|
176
|
+
padding !== undefined ||
|
|
177
|
+
paddingLeft !== undefined ||
|
|
178
|
+
paddingRight !== undefined ||
|
|
179
|
+
paddingTop !== undefined ||
|
|
180
|
+
paddingBottom !== undefined;
|
|
181
|
+
|
|
182
|
+
// Tab styles based on variant
|
|
183
|
+
if (isStandardVariant) {
|
|
184
|
+
// Standard variant: underline style (NEW)
|
|
185
|
+
const defaultPaddingClass = hasPaddingInStyle ? "" : "px-3 py-4";
|
|
186
|
+
const finalColor =
|
|
187
|
+
styleColor !== undefined
|
|
188
|
+
? styleColor
|
|
189
|
+
: selected
|
|
190
|
+
? "hsl(var(--primary))"
|
|
191
|
+
: "hsl(var(--muted-foreground))";
|
|
192
|
+
|
|
193
|
+
const { onClick: propsOnClick, ...restProps } = props;
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<button
|
|
197
|
+
ref={ref}
|
|
198
|
+
type="button"
|
|
199
|
+
role="tab"
|
|
200
|
+
id={id}
|
|
201
|
+
aria-selected={selected}
|
|
202
|
+
aria-controls={ariaControls}
|
|
203
|
+
tabIndex={selected ? 0 : -1}
|
|
204
|
+
onClick={(e) => {
|
|
205
|
+
if (onClick) {
|
|
206
|
+
onClick(e);
|
|
207
|
+
}
|
|
208
|
+
if (propsOnClick) {
|
|
209
|
+
propsOnClick(e);
|
|
210
|
+
}
|
|
211
|
+
}}
|
|
212
|
+
onKeyDown={(e) => {
|
|
213
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
214
|
+
e.preventDefault();
|
|
215
|
+
if (onClick) {
|
|
216
|
+
onClick(e);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}}
|
|
220
|
+
className={cn(
|
|
221
|
+
"cursor-pointer transition-all duration-200 text-sm border-none bg-transparent focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
222
|
+
defaultPaddingClass,
|
|
223
|
+
"min-w-[72px] min-h-[48px] flex-1",
|
|
224
|
+
selected ? "font-semibold" : "font-medium",
|
|
225
|
+
className
|
|
226
|
+
)}
|
|
227
|
+
style={{
|
|
228
|
+
color: finalColor,
|
|
229
|
+
textTransform: "none",
|
|
230
|
+
flex: 1,
|
|
231
|
+
minWidth: 0,
|
|
232
|
+
padding: padding,
|
|
233
|
+
paddingLeft: paddingLeft,
|
|
234
|
+
paddingRight: paddingRight,
|
|
235
|
+
paddingTop: paddingTop,
|
|
236
|
+
paddingBottom: paddingBottom,
|
|
237
|
+
margin: margin,
|
|
238
|
+
marginLeft: marginLeft,
|
|
239
|
+
marginRight: marginRight,
|
|
240
|
+
...restStyle,
|
|
241
|
+
}}
|
|
242
|
+
onMouseEnter={(e) => {
|
|
243
|
+
if (!selected) {
|
|
244
|
+
e.currentTarget.style.backgroundColor = "hsl(var(--accent))";
|
|
245
|
+
}
|
|
246
|
+
}}
|
|
247
|
+
onMouseLeave={(e) => {
|
|
248
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
249
|
+
}}
|
|
250
|
+
{...restProps}
|
|
251
|
+
>
|
|
252
|
+
{label}
|
|
253
|
+
</button>
|
|
254
|
+
);
|
|
255
|
+
} else {
|
|
256
|
+
// Pills variant: existing circle button style (DO NOT CHANGE - backward compatibility)
|
|
257
|
+
const getTabColors = () => {
|
|
258
|
+
if (selected) {
|
|
259
|
+
return {
|
|
260
|
+
color: "hsl(var(--primary-foreground))",
|
|
261
|
+
backgroundColor: "hsl(var(--primary))",
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
// Unselected tabs all have the same style
|
|
265
|
+
return {
|
|
266
|
+
color: "hsl(var(--foreground))",
|
|
267
|
+
backgroundColor: "hsl(var(--accent))",
|
|
268
|
+
};
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const tabColors = getTabColors();
|
|
272
|
+
|
|
273
|
+
const hasMarginInStyle =
|
|
274
|
+
margin !== undefined ||
|
|
275
|
+
marginLeft !== undefined ||
|
|
276
|
+
marginRight !== undefined;
|
|
277
|
+
|
|
278
|
+
// Default padding/margin classes, but allow override
|
|
279
|
+
const defaultPaddingClass = hasPaddingInStyle ? "" : "px-4 py-3";
|
|
280
|
+
const defaultMarginClass = hasMarginInStyle ? "" : "mx-2";
|
|
281
|
+
|
|
282
|
+
// Use style backgroundColor if provided, otherwise use tabColors
|
|
283
|
+
const finalBackgroundColor =
|
|
284
|
+
styleBackgroundColor !== undefined
|
|
285
|
+
? styleBackgroundColor
|
|
286
|
+
: tabColors.backgroundColor;
|
|
287
|
+
|
|
288
|
+
const finalColor =
|
|
289
|
+
styleColor !== undefined ? styleColor : tabColors.color;
|
|
290
|
+
|
|
291
|
+
// Ensure restStyle doesn't override backgroundColor or color
|
|
292
|
+
const { backgroundColor: _, color: __, ...cleanRestStyle } = restStyle;
|
|
293
|
+
|
|
294
|
+
// Extract onClick from props to avoid override
|
|
295
|
+
const { onClick: propsOnClick, ...restProps } = props;
|
|
296
|
+
|
|
297
|
+
return (
|
|
298
|
+
<button
|
|
299
|
+
ref={ref}
|
|
300
|
+
type="button"
|
|
301
|
+
role="tab"
|
|
302
|
+
id={id}
|
|
303
|
+
aria-selected={selected}
|
|
304
|
+
aria-controls={ariaControls}
|
|
305
|
+
tabIndex={selected ? 0 : -1}
|
|
306
|
+
onClick={(e) => {
|
|
307
|
+
if (onClick) {
|
|
308
|
+
onClick(e);
|
|
309
|
+
}
|
|
310
|
+
if (propsOnClick) {
|
|
311
|
+
propsOnClick(e);
|
|
312
|
+
}
|
|
313
|
+
}}
|
|
314
|
+
onKeyDown={(e) => {
|
|
315
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
316
|
+
e.preventDefault();
|
|
317
|
+
if (onClick) {
|
|
318
|
+
onClick(e);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}}
|
|
322
|
+
className={cn(
|
|
323
|
+
"cursor-pointer transition-all duration-200 min-h-[32px] min-w-[100px] rounded-2xl text-sm border-none focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
324
|
+
defaultPaddingClass,
|
|
325
|
+
defaultMarginClass,
|
|
326
|
+
selected ? "font-semibold" : "font-medium",
|
|
327
|
+
className
|
|
328
|
+
)}
|
|
329
|
+
style={{
|
|
330
|
+
color: finalColor,
|
|
331
|
+
backgroundColor: finalBackgroundColor,
|
|
332
|
+
...(padding !== undefined && { padding }),
|
|
333
|
+
...(paddingLeft !== undefined && { paddingLeft }),
|
|
334
|
+
...(paddingRight !== undefined && { paddingRight }),
|
|
335
|
+
...(paddingTop !== undefined && { paddingTop }),
|
|
336
|
+
...(paddingBottom !== undefined && { paddingBottom }),
|
|
337
|
+
...(margin !== undefined && { margin }),
|
|
338
|
+
...(marginLeft !== undefined && { marginLeft }),
|
|
339
|
+
...(marginRight !== undefined && { marginRight }),
|
|
340
|
+
...cleanRestStyle,
|
|
341
|
+
}}
|
|
342
|
+
onMouseEnter={(e) => {
|
|
343
|
+
if (!selected) {
|
|
344
|
+
e.currentTarget.style.backgroundColor = "hsl(var(--accent))";
|
|
345
|
+
} else {
|
|
346
|
+
e.currentTarget.style.backgroundColor =
|
|
347
|
+
"hsl(var(--primary) / 0.9)";
|
|
348
|
+
}
|
|
349
|
+
}}
|
|
350
|
+
onMouseLeave={(e) => {
|
|
351
|
+
e.currentTarget.style.backgroundColor = finalBackgroundColor;
|
|
352
|
+
}}
|
|
353
|
+
{...restProps}
|
|
354
|
+
>
|
|
355
|
+
{label}
|
|
356
|
+
</button>
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
);
|
|
361
|
+
Tab.displayName = "Tab";
|
|
362
|
+
|
|
363
|
+
export { Tabs, Tab };
|