@wandelbots/wandelbots-js-react-components 2.34.0 → 2.34.1-pr.feature-robot-precondition-list.372.c1de8ff

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 (64) hide show
  1. package/dist/components/AppHeader.d.ts +34 -0
  2. package/dist/components/AppHeader.d.ts.map +1 -0
  3. package/dist/components/CycleTimer.d.ts.map +1 -1
  4. package/dist/components/DataGrid.d.ts +66 -0
  5. package/dist/components/DataGrid.d.ts.map +1 -0
  6. package/dist/components/LogPanel.d.ts +38 -0
  7. package/dist/components/LogPanel.d.ts.map +1 -0
  8. package/dist/components/LogStore.d.ts +12 -0
  9. package/dist/components/LogStore.d.ts.map +1 -0
  10. package/dist/components/LogViewer.d.ts +46 -0
  11. package/dist/components/LogViewer.d.ts.map +1 -0
  12. package/dist/components/ProgramControl.d.ts +8 -2
  13. package/dist/components/ProgramControl.d.ts.map +1 -1
  14. package/dist/components/ProgramStateIndicator.d.ts +1 -1
  15. package/dist/components/ProgramStateIndicator.d.ts.map +1 -1
  16. package/dist/components/RobotCard.d.ts +100 -0
  17. package/dist/components/RobotCard.d.ts.map +1 -0
  18. package/dist/components/RobotListItem.d.ts +34 -0
  19. package/dist/components/RobotListItem.d.ts.map +1 -0
  20. package/dist/components/RobotSetupReadinessIndicator.d.ts +31 -0
  21. package/dist/components/RobotSetupReadinessIndicator.d.ts.map +1 -0
  22. package/dist/components/RobotSetupReadinessIndicator.test.d.ts +2 -0
  23. package/dist/components/RobotSetupReadinessIndicator.test.d.ts.map +1 -0
  24. package/dist/components/TabBar.d.ts +30 -0
  25. package/dist/components/TabBar.d.ts.map +1 -0
  26. package/dist/components/robots/Robot.d.ts +3 -2
  27. package/dist/components/robots/Robot.d.ts.map +1 -1
  28. package/dist/components/robots/manufacturerHomePositions.d.ts +21 -0
  29. package/dist/components/robots/manufacturerHomePositions.d.ts.map +1 -0
  30. package/dist/icons/DropdownArrowIcon.d.ts +3 -0
  31. package/dist/icons/DropdownArrowIcon.d.ts.map +1 -0
  32. package/dist/icons/index.d.ts +1 -0
  33. package/dist/icons/index.d.ts.map +1 -1
  34. package/dist/index.cjs +49 -49
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.ts +10 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +8772 -7044
  39. package/dist/index.js.map +1 -1
  40. package/dist/themes/createDarkTheme.d.ts.map +1 -1
  41. package/package.json +2 -1
  42. package/src/components/AppHeader.md +84 -0
  43. package/src/components/AppHeader.tsx +199 -0
  44. package/src/components/CycleTimer.tsx +43 -64
  45. package/src/components/DataGrid.tsx +659 -0
  46. package/src/components/LogPanel.tsx +69 -0
  47. package/src/components/LogStore.ts +44 -0
  48. package/src/components/LogViewer.tsx +370 -0
  49. package/src/components/ProgramControl.tsx +27 -12
  50. package/src/components/ProgramStateIndicator.tsx +25 -8
  51. package/src/components/RobotCard.tsx +559 -0
  52. package/src/components/RobotListItem.tsx +150 -0
  53. package/src/components/RobotSetupReadinessIndicator.test.tsx +60 -0
  54. package/src/components/RobotSetupReadinessIndicator.tsx +124 -0
  55. package/src/components/TabBar.tsx +144 -0
  56. package/src/components/robots/Robot.tsx +5 -2
  57. package/src/components/robots/manufacturerHomePositions.ts +76 -0
  58. package/src/i18n/locales/de/translations.json +7 -1
  59. package/src/i18n/locales/en/translations.json +7 -1
  60. package/src/icons/DropdownArrowIcon.tsx +13 -0
  61. package/src/icons/chevronDown.svg +3 -0
  62. package/src/icons/index.ts +1 -0
  63. package/src/index.ts +14 -0
  64. package/src/themes/createDarkTheme.ts +75 -1
@@ -0,0 +1,69 @@
1
+ import type { SxProps } from "@mui/material"
2
+ import { observer } from "mobx-react-lite"
3
+ import { useEffect, useMemo, useRef } from "react"
4
+ import { externalizeComponent } from "../externalizeComponent"
5
+ import { LogStore } from "./LogStore"
6
+ import { LogViewer } from "./LogViewer"
7
+
8
+ export type LogPanelProps = {
9
+ /** Log store instance to use, or create one automatically if not provided */
10
+ store?: LogStore
11
+ /** Height of the component */
12
+ height?: string | number
13
+ /** Additional styles */
14
+ sx?: SxProps
15
+ /** Ref to the log store for external access */
16
+ onStoreReady?: (store: LogStore) => void
17
+ }
18
+
19
+ /**
20
+ * A complete log panel component with built-in state management.
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * function MyComponent() {
25
+ * const [logStore, setLogStore] = useState<LogStore>()
26
+ *
27
+ * return (
28
+ * <LogPanel
29
+ * height={400}
30
+ * onStoreReady={setLogStore}
31
+ * />
32
+ * )
33
+ * }
34
+ *
35
+ * // Then use the store to add messages
36
+ * logStore?.addInfo("Operation completed successfully")
37
+ * logStore?.addError("Something went wrong")
38
+ * logStore?.addWarning("Warning message")
39
+ * ```
40
+ */
41
+ export const LogPanel = externalizeComponent(
42
+ observer((props: LogPanelProps) => {
43
+ const { store: externalStore, onStoreReady, ...logViewerProps } = props
44
+ const onStoreReadyRef = useRef(onStoreReady)
45
+
46
+ // Update ref when callback changes
47
+ useEffect(() => {
48
+ onStoreReadyRef.current = onStoreReady
49
+ }, [onStoreReady])
50
+
51
+ const store = useMemo(() => {
52
+ const logStore = externalStore || new LogStore()
53
+ onStoreReadyRef.current?.(logStore)
54
+ return logStore
55
+ }, [externalStore])
56
+
57
+ const handleClear = () => {
58
+ store.clearMessages()
59
+ }
60
+
61
+ return (
62
+ <LogViewer
63
+ {...logViewerProps}
64
+ messages={store.messages}
65
+ onClear={handleClear}
66
+ />
67
+ )
68
+ }),
69
+ )
@@ -0,0 +1,44 @@
1
+ import { action, makeObservable, observable } from "mobx"
2
+ import type { LogLevel, LogMessage } from "./LogViewer"
3
+
4
+ export class LogStore {
5
+ messages: LogMessage[] = []
6
+
7
+ constructor() {
8
+ makeObservable(this, {
9
+ messages: observable,
10
+ addMessage: action,
11
+ clearMessages: action,
12
+ })
13
+ }
14
+
15
+ addMessage = (message: string, level: LogLevel = "info") => {
16
+ const logMessage: LogMessage = {
17
+ id: Math.random().toString(36).substring(2, 11),
18
+ timestamp: new Date(),
19
+ message,
20
+ level,
21
+ }
22
+ this.messages.push(logMessage)
23
+ }
24
+
25
+ clearMessages = () => {
26
+ this.messages = []
27
+ }
28
+
29
+ addInfo = (message: string) => {
30
+ this.addMessage(message, "info")
31
+ }
32
+
33
+ addWarning = (message: string) => {
34
+ this.addMessage(message, "warning")
35
+ }
36
+
37
+ addError = (message: string) => {
38
+ this.addMessage(message, "error")
39
+ }
40
+
41
+ addDebug = (message: string) => {
42
+ this.addMessage(message, "debug")
43
+ }
44
+ }
@@ -0,0 +1,370 @@
1
+ import {
2
+ ContentCopy,
3
+ DescriptionOutlined as DocumentIcon,
4
+ ExpandLess,
5
+ ExpandMore,
6
+ } from "@mui/icons-material"
7
+ import type { SxProps } from "@mui/material"
8
+ import {
9
+ Box,
10
+ Button,
11
+ IconButton,
12
+ Paper,
13
+ Typography,
14
+ useTheme,
15
+ } from "@mui/material"
16
+ import { observer } from "mobx-react-lite"
17
+ import { useEffect, useRef, useState } from "react"
18
+ import { externalizeComponent } from "../externalizeComponent"
19
+
20
+ export type LogLevel = "info" | "error" | "warning" | "debug"
21
+
22
+ export type LogMessage = {
23
+ id: string
24
+ timestamp: Date
25
+ message: string
26
+ level: LogLevel
27
+ }
28
+
29
+ export type LogViewerProps = {
30
+ /** Log messages to display */
31
+ messages: LogMessage[]
32
+ /** Callback when clear button is clicked */
33
+ onClear?: () => void
34
+ /** Height of the component */
35
+ height?: string | number
36
+ /** Additional styles */
37
+ sx?: SxProps
38
+ }
39
+
40
+ /**
41
+ * Utility function to create a log message
42
+ */
43
+ export const createLogMessage = (
44
+ message: string,
45
+ level: LogLevel,
46
+ id?: string,
47
+ ): LogMessage => ({
48
+ id: id || `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
49
+ timestamp: new Date(),
50
+ message,
51
+ level,
52
+ })
53
+
54
+ /**
55
+ * Utility function to create a debug log message
56
+ */
57
+ export const createDebugMessage = (message: string, id?: string): LogMessage =>
58
+ createLogMessage(message, "debug", id)
59
+
60
+ /**
61
+ * Utility function to create an info log message
62
+ */
63
+ export const createInfoMessage = (message: string, id?: string): LogMessage =>
64
+ createLogMessage(message, "info", id)
65
+
66
+ /**
67
+ * Utility function to create a warning log message
68
+ */
69
+ export const createWarningMessage = (
70
+ message: string,
71
+ id?: string,
72
+ ): LogMessage => createLogMessage(message, "warning", id)
73
+
74
+ /**
75
+ * Utility function to create an error log message
76
+ */
77
+ export const createErrorMessage = (message: string, id?: string): LogMessage =>
78
+ createLogMessage(message, "error", id)
79
+
80
+ /**
81
+ * A log viewer component that displays timestamped log messages with different levels.
82
+ * Features a header with document icon and clear button, and scrollable message area.
83
+ */
84
+ export const LogViewer = externalizeComponent(
85
+ observer((props: LogViewerProps) => {
86
+ const { messages = [], onClear, height = 400, sx } = props
87
+ const theme = useTheme()
88
+ const scrollContainerRef = useRef<HTMLDivElement>(null)
89
+
90
+ // Auto-scroll to bottom when new messages are added
91
+ useEffect(() => {
92
+ if (messages.length === 0) return
93
+
94
+ const scrollContainer = scrollContainerRef.current
95
+ if (!scrollContainer) return
96
+
97
+ // Use a timeout to scroll after the DOM updates
98
+ const timeoutId = setTimeout(() => {
99
+ // Scroll the container to the bottom, not the entire browser
100
+ scrollContainer.scrollTop = scrollContainer.scrollHeight
101
+ }, 10)
102
+
103
+ return () => clearTimeout(timeoutId)
104
+ }, [messages.length])
105
+
106
+ const formatTimestamp = (timestamp: Date) => {
107
+ return timestamp.toLocaleTimeString("en-US", {
108
+ hour12: false,
109
+ hour: "2-digit",
110
+ minute: "2-digit",
111
+ second: "2-digit",
112
+ })
113
+ }
114
+
115
+ const getMessageColor = (level: LogLevel) => {
116
+ switch (level) {
117
+ case "error":
118
+ return theme.palette.error.main
119
+ case "warning":
120
+ return theme.palette.warning.main
121
+ case "info":
122
+ return theme.palette.info.main
123
+ case "debug":
124
+ return theme.palette.text.disabled
125
+ default:
126
+ return theme.palette.text.secondary
127
+ }
128
+ }
129
+
130
+ // Component for individual log messages with expand/copy functionality
131
+ const LogMessage = ({ message }: { message: LogMessage }) => {
132
+ const [isExpanded, setIsExpanded] = useState(false)
133
+ const [copyTooltip, setCopyTooltip] = useState(false)
134
+ const [isHovered, setIsHovered] = useState(false)
135
+ const isLongMessage = message.message.length > 150
136
+
137
+ const handleCopy = async () => {
138
+ try {
139
+ await navigator.clipboard.writeText(message.message)
140
+ setCopyTooltip(true)
141
+ setTimeout(() => setCopyTooltip(false), 2000)
142
+ } catch (err) {
143
+ console.error("Failed to copy message:", err)
144
+ }
145
+ }
146
+
147
+ const displayMessage =
148
+ isLongMessage && !isExpanded
149
+ ? message.message.substring(0, 150) + "..."
150
+ : message.message
151
+
152
+ return (
153
+ <Box
154
+ key={message.id}
155
+ onMouseEnter={() => setIsHovered(true)}
156
+ onMouseLeave={() => setIsHovered(false)}
157
+ sx={{
158
+ display: "flex",
159
+ gap: 1,
160
+ fontFamily: "monospace",
161
+ flexDirection: "column",
162
+ "&:hover": {
163
+ backgroundColor: theme.palette.action.hover,
164
+ },
165
+ borderRadius: "4px",
166
+ padding: "2px 4px",
167
+ margin: "-2px -4px",
168
+ }}
169
+ >
170
+ <Box sx={{ display: "flex", gap: 1 }}>
171
+ {/* Timestamp */}
172
+ <Typography
173
+ component="span"
174
+ sx={{
175
+ fontWeight: 400,
176
+ fontSize: "12px",
177
+ lineHeight: "18px",
178
+ letterSpacing: "0.4px",
179
+ color: theme.palette.text.disabled,
180
+ whiteSpace: "nowrap",
181
+ flexShrink: 0,
182
+ }}
183
+ >
184
+ [{formatTimestamp(message.timestamp)}]
185
+ </Typography>
186
+
187
+ {/* Message */}
188
+ <Typography
189
+ component="span"
190
+ sx={{
191
+ fontWeight: 400,
192
+ fontSize: "12px",
193
+ lineHeight: "18px",
194
+ letterSpacing: "0.4px",
195
+ color: getMessageColor(message.level),
196
+ wordBreak: "break-word",
197
+ overflowWrap: "anywhere",
198
+ hyphens: "auto",
199
+ flex: 1,
200
+ whiteSpace: "pre-wrap",
201
+ }}
202
+ >
203
+ {displayMessage}
204
+ </Typography>
205
+
206
+ {/* Action buttons - only visible on hover */}
207
+ <Box
208
+ sx={{
209
+ display: "flex",
210
+ alignItems: "flex-start",
211
+ gap: 0.5,
212
+ opacity: isHovered ? 1 : 0,
213
+ transition: "opacity 0.2s ease-in-out",
214
+ visibility: isHovered ? "visible" : "hidden",
215
+ }}
216
+ >
217
+ <IconButton
218
+ size="small"
219
+ onClick={handleCopy}
220
+ sx={{
221
+ padding: "2px",
222
+ color: theme.palette.text.secondary,
223
+ "&:hover": {
224
+ backgroundColor: theme.palette.action.hover,
225
+ },
226
+ }}
227
+ title={copyTooltip ? "Copied!" : "Copy message"}
228
+ >
229
+ <ContentCopy sx={{ fontSize: 12 }} />
230
+ </IconButton>
231
+
232
+ {isLongMessage && (
233
+ <IconButton
234
+ size="small"
235
+ onClick={() => setIsExpanded(!isExpanded)}
236
+ sx={{
237
+ padding: "2px",
238
+ color: theme.palette.text.secondary,
239
+ "&:hover": {
240
+ backgroundColor: theme.palette.action.hover,
241
+ },
242
+ }}
243
+ title={isExpanded ? "Collapse" : "Expand"}
244
+ >
245
+ {isExpanded ? (
246
+ <ExpandLess sx={{ fontSize: 12 }} />
247
+ ) : (
248
+ <ExpandMore sx={{ fontSize: 12 }} />
249
+ )}
250
+ </IconButton>
251
+ )}
252
+ </Box>
253
+ </Box>
254
+ </Box>
255
+ )
256
+ }
257
+
258
+ return (
259
+ <Paper
260
+ sx={{
261
+ backgroundColor:
262
+ theme.palette.backgroundPaperElevation?.[2] || "#171927",
263
+ backgroundImage: "none", // Override any gradient from elevation
264
+ height,
265
+ display: "flex",
266
+ flexDirection: "column",
267
+ overflow: "hidden",
268
+ ...sx,
269
+ }}
270
+ >
271
+ {/* Header */}
272
+ <Box
273
+ sx={{
274
+ display: "flex",
275
+ alignItems: "center",
276
+ justifyContent: "space-between",
277
+ padding: "12px 16px",
278
+ }}
279
+ >
280
+ <Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
281
+ <DocumentIcon
282
+ sx={{
283
+ fontSize: 16,
284
+ color: theme.palette.action.active,
285
+ opacity: 0.56,
286
+ }}
287
+ />
288
+ <Typography
289
+ sx={{
290
+ fontWeight: 500,
291
+ fontSize: "14px",
292
+ lineHeight: "143%",
293
+ letterSpacing: "0.17px",
294
+ color: theme.palette.text.primary,
295
+ }}
296
+ >
297
+ Log
298
+ </Typography>
299
+ </Box>
300
+ <Button
301
+ onClick={onClear}
302
+ variant="text"
303
+ sx={{
304
+ fontWeight: 500,
305
+ fontSize: "13px",
306
+ lineHeight: "22px",
307
+ letterSpacing: "0.46px",
308
+ color: theme.palette.primary.main,
309
+ textTransform: "none",
310
+ minWidth: "auto",
311
+ padding: "4px 8px",
312
+ "&:hover": {
313
+ backgroundColor: theme.palette.primary.main + "14", // 8% opacity
314
+ },
315
+ }}
316
+ >
317
+ Clear
318
+ </Button>
319
+ </Box>
320
+
321
+ {/* Messages Container */}
322
+ <Box
323
+ ref={scrollContainerRef}
324
+ sx={{
325
+ flex: 1,
326
+ overflow: "auto",
327
+ padding: "8px 16px",
328
+ display: "flex",
329
+ flexDirection: "column",
330
+ gap: "2px",
331
+ // Custom scrollbar styling to keep it consistently dark
332
+ scrollbarWidth: "thin",
333
+ scrollbarColor: `${theme.palette.divider} transparent`,
334
+ "&::-webkit-scrollbar": {
335
+ width: "6px",
336
+ },
337
+ "&::-webkit-scrollbar-track": {
338
+ background: "transparent",
339
+ },
340
+ "&::-webkit-scrollbar-thumb": {
341
+ backgroundColor: theme.palette.divider,
342
+ borderRadius: "3px",
343
+ "&:hover": {
344
+ backgroundColor: theme.palette.divider,
345
+ },
346
+ },
347
+ }}
348
+ >
349
+ {messages.length === 0 ? (
350
+ <Typography
351
+ sx={{
352
+ color: theme.palette.text.disabled,
353
+ fontSize: "12px",
354
+ fontStyle: "italic",
355
+ textAlign: "center",
356
+ marginTop: 2,
357
+ }}
358
+ >
359
+ No log messages
360
+ </Typography>
361
+ ) : (
362
+ messages.map((message) => (
363
+ <LogMessage key={message.id} message={message} />
364
+ ))
365
+ )}
366
+ </Box>
367
+ </Paper>
368
+ )
369
+ }),
370
+ )
@@ -4,7 +4,13 @@ import { observer } from "mobx-react-lite"
4
4
  import { useTranslation } from "react-i18next"
5
5
  import { externalizeComponent } from "../externalizeComponent"
6
6
 
7
- export type ProgramState = "idle" | "running" | "paused" | "stopping"
7
+ export enum ProgramState {
8
+ IDLE = "idle",
9
+ RUNNING = "running",
10
+ PAUSED = "paused",
11
+ STOPPING = "stopping",
12
+ ERROR = "error",
13
+ }
8
14
 
9
15
  export interface ProgramControlProps {
10
16
  /** The current state of the program control */
@@ -46,7 +52,7 @@ interface ButtonConfig {
46
52
  * A control component for program execution with run, pause, and stop functionality.
47
53
  *
48
54
  * Features:
49
- * - State machine with idle, running, paused, and stopping states
55
+ * - State machine with idle, running, paused, stopping, and error states
50
56
  * - Two variants: with_pause (3 buttons) and without_pause (2 buttons)
51
57
  * - Optional manual reset functionality
52
58
  * - Responsive design with 110px circular buttons
@@ -70,22 +76,28 @@ export const ProgramControl = externalizeComponent(
70
76
  const getButtonConfigs = (): ButtonConfig[] => {
71
77
  const baseConfigs: Record<string, ButtonConfig> = {
72
78
  run: {
73
- enabled: state === "idle" || state === "paused",
79
+ enabled:
80
+ state === ProgramState.IDLE ||
81
+ state === ProgramState.PAUSED ||
82
+ state === ProgramState.ERROR,
74
83
  label:
75
- state === "paused"
84
+ state === ProgramState.PAUSED
76
85
  ? t("ProgramControl.Resume.bt")
77
- : t("ProgramControl.Start.bt"),
86
+ : state === ProgramState.ERROR
87
+ ? t("ProgramControl.Retry.bt")
88
+ : t("ProgramControl.Start.bt"),
78
89
  color: theme.palette.success.main,
79
90
  onClick: onRun,
80
91
  },
81
92
  pause: {
82
- enabled: state === "running",
93
+ enabled: state === ProgramState.RUNNING,
83
94
  label: t("ProgramControl.Pause.bt"),
84
95
  color: "#FFFFFF33",
85
96
  onClick: onPause || (() => {}),
86
97
  },
87
98
  stop: {
88
- enabled: state === "running" || state === "paused",
99
+ enabled:
100
+ state === ProgramState.RUNNING || state === ProgramState.PAUSED,
89
101
  label: t("ProgramControl.Stop.bt"),
90
102
  color: theme.palette.error.main,
91
103
  onClick: onStop,
@@ -157,7 +169,7 @@ export const ProgramControl = externalizeComponent(
157
169
  variant="contained"
158
170
  disabled={
159
171
  !config.enabled ||
160
- (state === "stopping" && !requiresManualReset)
172
+ (state === ProgramState.STOPPING && !requiresManualReset)
161
173
  }
162
174
  onClick={config.onClick}
163
175
  sx={{
@@ -167,14 +179,17 @@ export const ProgramControl = externalizeComponent(
167
179
  backgroundColor: config.color,
168
180
  opacity:
169
181
  config.enabled &&
170
- !(state === "stopping" && !requiresManualReset)
182
+ !(state === ProgramState.STOPPING && !requiresManualReset)
171
183
  ? 1
172
184
  : 0.3,
173
185
  "&:hover": {
174
186
  backgroundColor: config.color,
175
187
  opacity:
176
188
  config.enabled &&
177
- !(state === "stopping" && !requiresManualReset)
189
+ !(
190
+ state === ProgramState.STOPPING &&
191
+ !requiresManualReset
192
+ )
178
193
  ? 0.8
179
194
  : 0.3,
180
195
  },
@@ -194,13 +209,13 @@ export const ProgramControl = externalizeComponent(
194
209
  sx={{
195
210
  color:
196
211
  config.enabled &&
197
- !(state === "stopping" && !requiresManualReset)
212
+ !(state === ProgramState.STOPPING && !requiresManualReset)
198
213
  ? config.color
199
214
  : theme.palette.text.disabled,
200
215
  textAlign: "center",
201
216
  opacity:
202
217
  config.enabled &&
203
- !(state === "stopping" && !requiresManualReset)
218
+ !(state === ProgramState.STOPPING && !requiresManualReset)
204
219
  ? 1
205
220
  : 0.3,
206
221
  }}
@@ -1,4 +1,4 @@
1
- import { Chip, useTheme } from "@mui/material"
1
+ import { Chip, Typography, useTheme } from "@mui/material"
2
2
  import type {
3
3
  RobotControllerStateOperationModeEnum,
4
4
  RobotControllerStateSafetyStateEnum,
@@ -6,7 +6,7 @@ import type {
6
6
  import { observer } from "mobx-react-lite"
7
7
  import { useTranslation } from "react-i18next"
8
8
  import { externalizeComponent } from "../externalizeComponent"
9
- import type { ProgramState } from "./ProgramControl"
9
+ import { ProgramState } from "./ProgramControl"
10
10
 
11
11
  export interface ProgramStateIndicatorProps {
12
12
  /** The current state of the program */
@@ -77,22 +77,27 @@ export const ProgramStateIndicator = externalizeComponent(
77
77
  // For normal safety states, check program state
78
78
  if (safetyState === "SAFETY_STATE_NORMAL") {
79
79
  switch (programState) {
80
- case "running":
80
+ case ProgramState.RUNNING:
81
81
  return {
82
82
  label: t("ProgramStateIndicator.Running.lb"),
83
83
  color: theme.palette.success.main,
84
84
  }
85
- case "paused":
85
+ case ProgramState.PAUSED:
86
86
  return {
87
87
  label: t("ProgramStateIndicator.Paused.lb"),
88
88
  color: theme.palette.grey[600],
89
89
  }
90
- case "stopping":
90
+ case ProgramState.STOPPING:
91
91
  return {
92
92
  label: t("ProgramStateIndicator.Stopped.lb"),
93
93
  color: theme.palette.error.main,
94
94
  }
95
- case "idle":
95
+ case ProgramState.ERROR:
96
+ return {
97
+ label: t("ProgramStateIndicator.Error.lb"),
98
+ color: theme.palette.error.main,
99
+ }
100
+ case ProgramState.IDLE:
96
101
  default:
97
102
  return {
98
103
  label: t("ProgramStateIndicator.Ready.lb"),
@@ -131,14 +136,26 @@ export const ProgramStateIndicator = externalizeComponent(
131
136
  return (
132
137
  <Chip
133
138
  className={className}
134
- label={fullLabel}
139
+ label={
140
+ <Typography
141
+ variant="body2"
142
+ sx={{
143
+ fontSize: "0.75rem", // Smaller than body2
144
+ lineHeight: 1.2,
145
+ }}
146
+ >
147
+ {fullLabel}
148
+ </Typography>
149
+ }
135
150
  variant="filled"
136
151
  sx={{
137
152
  backgroundColor: color,
138
153
  color: theme.palette.getContrastText(color),
139
154
  fontWeight: 500,
155
+ height: "auto",
140
156
  "& .MuiChip-label": {
141
- paddingX: 2,
157
+ paddingX: 1.5,
158
+ paddingY: 0.5,
142
159
  },
143
160
  }}
144
161
  />