@wandelbots/wandelbots-js-react-components 2.53.0 → 2.54.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wandelbots/wandelbots-js-react-components",
3
- "version": "2.53.0",
3
+ "version": "2.54.0",
4
4
  "description": "React UI toolkit for building applications on top of the Wandelbots platform",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -13,6 +13,24 @@ export interface TabItem {
13
13
  content: React.ReactNode
14
14
  /** Optional icon component to display with the tab */
15
15
  icon?: React.ReactElement
16
+ /** Optional badge configuration */
17
+ badge?: {
18
+ /** Badge content (number or string) */
19
+ content: React.ReactNode
20
+ /** Badge color variant */
21
+ color?:
22
+ | "default"
23
+ | "primary"
24
+ | "secondary"
25
+ | "error"
26
+ | "info"
27
+ | "success"
28
+ | "warning"
29
+ /** Maximum number to display (e.g., 99+) */
30
+ max?: number
31
+ /** Show badge even when content is zero */
32
+ showZero?: boolean
33
+ }
16
34
  }
17
35
 
18
36
  export interface TabBarProps {
@@ -101,62 +119,158 @@ export const TabBar = externalizeComponent(
101
119
  sx={{ height: "100%", display: "flex", flexDirection: "column", ...sx }}
102
120
  >
103
121
  {/* Tabs */}
104
- <Box sx={{ px: 0, py: 0 }}>
122
+ <Box
123
+ sx={{
124
+ px: 0,
125
+ py: 0,
126
+ overflow: "visible",
127
+ // Extra padding to prevent badge clipping
128
+ // Top: accommodates badge positioned at top: -6px with 20px height
129
+ // Right: accommodates badge positioned at right: -8px with 20px width
130
+ paddingTop: (theme) => theme.spacing(2), // 16px
131
+ paddingRight: (theme) => theme.spacing(2.5), // 20px
132
+ }}
133
+ >
105
134
  <Tabs
106
135
  value={currentValue}
107
136
  onChange={handleTabChange}
108
137
  sx={{
109
138
  minHeight: "32px",
110
139
  backgroundColor: "transparent",
140
+ overflow: "visible",
111
141
  "& .MuiTabs-indicator": {
112
142
  display: "none", // Hide the default indicator
113
143
  },
114
144
  "& .MuiTabs-flexContainer": {
115
145
  gap: 2,
146
+ overflow: "visible",
147
+ paddingTop: 0,
148
+ paddingBottom: 0,
149
+ },
150
+ "& .MuiTabs-scroller": {
151
+ overflow: "visible !important",
152
+ },
153
+ "& .MuiTab-root": {
154
+ overflow: "visible",
116
155
  },
117
156
  }}
118
157
  >
119
- {items.map((item, index) => (
120
- <Tab
121
- key={item.id}
122
- label={item.label}
123
- icon={item.icon}
124
- iconPosition="start"
125
- disableRipple
126
- sx={{
127
- minHeight: "32px",
128
- height: "32px",
129
- padding: "0px 10px",
130
- borderRadius: "12px",
131
- backgroundColor: (theme) =>
132
- theme.palette.backgroundPaperElevation?.[11] || "#32344B",
133
- color: "text.primary",
134
- opacity: currentValue === index ? 1 : 0.38,
135
- fontSize: "13px",
136
- transition: "all 0.2s ease-in-out",
137
- "&:hover": {
138
- opacity: currentValue === index ? 1 : 0.6,
139
- },
140
- "&.Mui-selected": {
141
- opacity: 1,
158
+ {items.map((item, index) => {
159
+ // Helper functions for badge logic
160
+ const getBadgeContent = () => {
161
+ if (!item.badge) return ""
162
+ const content = item.badge.content
163
+ const max = item.badge.max
164
+
165
+ // Handle numeric content with max limit
166
+ if (typeof content === "number" && max && content > max) {
167
+ return `${max}+`
168
+ }
169
+
170
+ return String(content)
171
+ }
172
+
173
+ const shouldShowBadge = () => {
174
+ if (!item.badge) return false
175
+ const content = item.badge.content
176
+ const showZero = item.badge.showZero
177
+
178
+ // If content is 0 and showZero is false, hide badge
179
+ if (content === 0 && !showZero) return false
180
+
181
+ return true
182
+ }
183
+
184
+ const badgeContent = getBadgeContent()
185
+ const showBadge = shouldShowBadge()
186
+
187
+ return (
188
+ <Tab
189
+ key={item.id}
190
+ label={item.label}
191
+ icon={item.icon}
192
+ iconPosition="start"
193
+ disableRipple
194
+ sx={{
195
+ minHeight: "32px",
196
+ height: "32px",
197
+ padding: "0px 10px",
198
+ borderRadius: "12px",
142
199
  backgroundColor: (theme) =>
143
200
  theme.palette.backgroundPaperElevation?.[11] || "#32344B",
144
201
  color: "text.primary",
145
- },
146
- "&:focus": {
147
- outline: "none",
148
- },
149
- "&:active": {
150
- transform: "none",
151
- },
152
- }}
153
- />
154
- ))}
202
+ opacity: currentValue === index ? 1 : 0.38,
203
+ fontSize: "13px",
204
+ transition: "all 0.2s ease-in-out",
205
+ position: "relative",
206
+ "&:hover": {
207
+ opacity: currentValue === index ? 1 : 0.6,
208
+ },
209
+ "&.Mui-selected": {
210
+ opacity: 1,
211
+ backgroundColor: (theme) =>
212
+ theme.palette.backgroundPaperElevation?.[11] ||
213
+ "#32344B",
214
+ color: "text.primary",
215
+ },
216
+ "&:focus": {
217
+ outline: "none",
218
+ },
219
+ "&:active": {
220
+ transform: "none",
221
+ },
222
+ ...(showBadge && {
223
+ "&::after": {
224
+ content: `"${badgeContent}"`,
225
+ position: "absolute",
226
+ // Position badge in top-right corner, extending outside tab bounds
227
+ top: -6, // Positions badge above the tab
228
+ right: -8, // Positions badge to the right of the tab
229
+ // Badge sizing - MUI Badge standard small size
230
+ minWidth: "20px", // Min width for single digits
231
+ height: "20px", // Fixed height for consistent appearance
232
+ borderRadius: "10px", // Half of height for circular shape
233
+ backgroundColor: (theme) => {
234
+ const colors = {
235
+ error: theme.palette.error.main,
236
+ info: theme.palette.info.main,
237
+ success: theme.palette.success.main,
238
+ warning: theme.palette.warning.main,
239
+ primary: theme.palette.primary.main,
240
+ secondary: theme.palette.secondary.main,
241
+ default: theme.palette.grey[500],
242
+ }
243
+ return (
244
+ colors[item.badge?.color || "error"] || colors.error
245
+ )
246
+ },
247
+ color: "white",
248
+ fontSize: "12px", // Readable size for badge content
249
+ fontWeight: 600, // Semi-bold for better visibility
250
+ display: "flex",
251
+ alignItems: "center",
252
+ justifyContent: "center",
253
+ padding: "2px 6px", // Horizontal padding for wider content (e.g., "99+")
254
+ zIndex: 1,
255
+ pointerEvents: "none", // Allow clicks to pass through to tab
256
+ boxShadow: "0 2px 4px rgba(0,0,0,0.2)", // Subtle depth
257
+ },
258
+ }),
259
+ }}
260
+ />
261
+ )
262
+ })}
155
263
  </Tabs>
156
264
  </Box>
157
265
 
158
266
  {/* Border line */}
159
- <Box sx={{ mt: "32px", borderBottom: 1, borderColor: "divider" }} />
267
+ <Box
268
+ sx={{
269
+ mt: (theme) => theme.spacing(2),
270
+ borderBottom: 1,
271
+ borderColor: "divider",
272
+ }}
273
+ />
160
274
 
161
275
  {/* Tab Content */}
162
276
  <Box sx={{ flex: 1, overflow: "auto" }}>