@wandelbots/wandelbots-js-react-components 2.40.0-pr.feature-seperate-timer.383.0494cf4 → 2.41.0-pr.feature-seperate-timer.383.cd7e408

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.40.0-pr.feature-seperate-timer.383.0494cf4",
3
+ "version": "2.41.0-pr.feature-seperate-timer.383.cd7e408",
4
4
  "description": "React UI toolkit for building applications on top of the Wandelbots platform",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -1,7 +1,7 @@
1
1
  import type { SxProps } from "@mui/material"
2
2
  import { Box, Tab, Tabs } from "@mui/material"
3
3
  import { observer } from "mobx-react-lite"
4
- import { useState } from "react"
4
+ import { useEffect, useState } from "react"
5
5
  import { externalizeComponent } from "../externalizeComponent"
6
6
 
7
7
  export interface TabItem {
@@ -18,6 +18,8 @@ export interface TabItem {
18
18
  export interface TabBarProps {
19
19
  /** Array of tab items to display */
20
20
  items: TabItem[]
21
+ /** Controlled active tab index */
22
+ activeTab?: number
21
23
  /** Default active tab index */
22
24
  defaultActiveTab?: number
23
25
  /** Callback when tab changes */
@@ -57,14 +59,39 @@ function TabPanel(props: TabPanelProps) {
57
59
  */
58
60
  export const TabBar = externalizeComponent(
59
61
  observer((props: TabBarProps) => {
60
- const { items, defaultActiveTab = 0, onTabChange, sx, ref } = props
61
- const [activeTab, setActiveTab] = useState(defaultActiveTab)
62
+ const {
63
+ items,
64
+ activeTab,
65
+ defaultActiveTab = 0,
66
+ onTabChange,
67
+ sx,
68
+ ref,
69
+ } = props
70
+ const isControlled = activeTab !== undefined
71
+ const [uncontrolledActiveTab, setUncontrolledActiveTab] =
72
+ useState(defaultActiveTab)
73
+
74
+ // Keep uncontrolled state in range when items change
75
+ useEffect(() => {
76
+ if (isControlled) return
77
+ if (items.length === 0) return
78
+ if (
79
+ uncontrolledActiveTab < 0 ||
80
+ uncontrolledActiveTab > items.length - 1
81
+ ) {
82
+ setUncontrolledActiveTab(0)
83
+ }
84
+ }, [items.length, isControlled, uncontrolledActiveTab])
85
+
86
+ const currentValue = isControlled ? activeTab! : uncontrolledActiveTab
62
87
 
63
88
  const handleTabChange = (
64
89
  _event: React.SyntheticEvent,
65
90
  newValue: number,
66
91
  ) => {
67
- setActiveTab(newValue)
92
+ if (!isControlled) {
93
+ setUncontrolledActiveTab(newValue)
94
+ }
68
95
  onTabChange?.(newValue)
69
96
  }
70
97
 
@@ -76,7 +103,7 @@ export const TabBar = externalizeComponent(
76
103
  {/* Tabs */}
77
104
  <Box sx={{ px: 0, py: 0 }}>
78
105
  <Tabs
79
- value={activeTab}
106
+ value={currentValue}
80
107
  onChange={handleTabChange}
81
108
  sx={{
82
109
  minHeight: "32px",
@@ -104,11 +131,11 @@ export const TabBar = externalizeComponent(
104
131
  backgroundColor: (theme) =>
105
132
  theme.palette.backgroundPaperElevation?.[11] || "#32344B",
106
133
  color: "text.primary",
107
- opacity: activeTab === index ? 1 : 0.38,
134
+ opacity: currentValue === index ? 1 : 0.38,
108
135
  fontSize: "13px",
109
136
  transition: "all 0.2s ease-in-out",
110
137
  "&:hover": {
111
- opacity: activeTab === index ? 1 : 0.6,
138
+ opacity: currentValue === index ? 1 : 0.6,
112
139
  },
113
140
  "&.Mui-selected": {
114
141
  opacity: 1,
@@ -134,7 +161,7 @@ export const TabBar = externalizeComponent(
134
161
  {/* Tab Content */}
135
162
  <Box sx={{ flex: 1, overflow: "auto" }}>
136
163
  {items.map((item, index) => (
137
- <TabPanel key={item.id} value={activeTab} index={index}>
164
+ <TabPanel key={item.id} value={currentValue} index={index}>
138
165
  {item.content}
139
166
  </TabPanel>
140
167
  ))}
@@ -7,6 +7,7 @@ import type {
7
7
  } from "@wandelbots/nova-js/v1"
8
8
  import { observer } from "mobx-react-lite"
9
9
  import { useRef, useState } from "react"
10
+ import { externalizeComponent } from "../../externalizeComponent"
10
11
  import { CopyableText } from "../CopyableText"
11
12
  import { useAnimationFrame } from "../utils/hooks"
12
13
 
@@ -39,81 +40,83 @@ export type PoseCartesianValuesProps = {
39
40
  showCopyButton?: boolean
40
41
  }
41
42
 
42
- export const PoseCartesianValues = observer(
43
- ({
44
- motionStream,
45
- connectedMotionGroup,
46
- showCopyButton = false,
47
- }: PoseCartesianValuesProps) => {
48
- const poseHolderRef = useRef<HTMLDivElement>(null)
49
- const [copyMessage, setCopyMessage] = useState("")
50
-
51
- const activeMotionStream = createMotionStateProvider(
43
+ export const PoseCartesianValues = externalizeComponent(
44
+ observer(
45
+ ({
52
46
  motionStream,
53
47
  connectedMotionGroup,
54
- )
48
+ showCopyButton = false,
49
+ }: PoseCartesianValuesProps) => {
50
+ const poseHolderRef = useRef<HTMLDivElement>(null)
51
+ const [copyMessage, setCopyMessage] = useState("")
55
52
 
56
- if (!activeMotionStream) {
57
- throw new Error(
58
- "PoseCartesianValues requires either motionStream or connectedMotionGroup prop",
53
+ const activeMotionStream = createMotionStateProvider(
54
+ motionStream,
55
+ connectedMotionGroup,
59
56
  )
60
- }
61
-
62
- function getCurrentPoseString() {
63
- if (!activeMotionStream) return ""
64
- const tcpPose = activeMotionStream.rapidlyChangingMotionState.tcp_pose
65
- if (!tcpPose) return ""
66
- return poseToWandelscriptString(tcpPose)
67
- }
68
57
 
69
- const handleCopy = async () => {
70
- try {
71
- await navigator.clipboard.writeText(getCurrentPoseString())
72
- setCopyMessage("Copied!")
73
- setTimeout(() => setCopyMessage(""), 2000)
74
- } catch {
75
- setCopyMessage("Copy failed")
76
- setTimeout(() => setCopyMessage(""), 2000)
58
+ if (!activeMotionStream) {
59
+ throw new Error(
60
+ "PoseCartesianValues requires either motionStream or connectedMotionGroup prop",
61
+ )
77
62
  }
78
- }
79
63
 
80
- useAnimationFrame(() => {
81
- if (!poseHolderRef.current) {
82
- return
64
+ function getCurrentPoseString() {
65
+ if (!activeMotionStream) return ""
66
+ const tcpPose = activeMotionStream.rapidlyChangingMotionState.tcp_pose
67
+ if (!tcpPose) return ""
68
+ return poseToWandelscriptString(tcpPose)
83
69
  }
84
- const newPoseContent = getCurrentPoseString()
85
- if (poseHolderRef.current.textContent === newPoseContent) {
86
- return
70
+
71
+ const handleCopy = async () => {
72
+ try {
73
+ await navigator.clipboard.writeText(getCurrentPoseString())
74
+ setCopyMessage("Copied!")
75
+ setTimeout(() => setCopyMessage(""), 2000)
76
+ } catch {
77
+ setCopyMessage("Copy failed")
78
+ setTimeout(() => setCopyMessage(""), 2000)
79
+ }
87
80
  }
88
81
 
89
- poseHolderRef.current.textContent = newPoseContent
90
- })
82
+ useAnimationFrame(() => {
83
+ if (!poseHolderRef.current) {
84
+ return
85
+ }
86
+ const newPoseContent = getCurrentPoseString()
87
+ if (poseHolderRef.current.textContent === newPoseContent) {
88
+ return
89
+ }
91
90
 
92
- return (
93
- <Stack
94
- direction="row"
95
- alignItems="center"
96
- spacing={1}
97
- sx={{ flexGrow: 1, minWidth: 0, overflow: "hidden" }}
98
- >
99
- <CopyableText value={getCurrentPoseString()} ref={poseHolderRef} />
100
- {showCopyButton && (
101
- <Button
102
- variant="contained"
103
- color="secondary"
104
- size="small"
105
- onClick={handleCopy}
106
- sx={{ flexShrink: 0 }}
107
- >
108
- Copy
109
- </Button>
110
- )}
111
- {copyMessage && (
112
- <Typography variant="caption" color="success.main">
113
- {copyMessage}
114
- </Typography>
115
- )}
116
- </Stack>
117
- )
118
- },
91
+ poseHolderRef.current.textContent = newPoseContent
92
+ })
93
+
94
+ return (
95
+ <Stack
96
+ direction="row"
97
+ alignItems="center"
98
+ spacing={1}
99
+ sx={{ flexGrow: 1, minWidth: 0, overflow: "hidden" }}
100
+ >
101
+ <CopyableText value={getCurrentPoseString()} ref={poseHolderRef} />
102
+ {showCopyButton && (
103
+ <Button
104
+ variant="contained"
105
+ color="secondary"
106
+ size="small"
107
+ onClick={handleCopy}
108
+ sx={{ flexShrink: 0 }}
109
+ >
110
+ Copy
111
+ </Button>
112
+ )}
113
+ {copyMessage && (
114
+ <Typography variant="caption" color="success.main">
115
+ {copyMessage}
116
+ </Typography>
117
+ )}
118
+ </Stack>
119
+ )
120
+ },
121
+ ),
119
122
  )
@@ -6,6 +6,7 @@ import type {
6
6
  } from "@wandelbots/nova-js/v1"
7
7
  import { observer } from "mobx-react-lite"
8
8
  import { useRef, useState } from "react"
9
+ import { externalizeComponent } from "../../externalizeComponent"
9
10
  import { CopyableText } from "../CopyableText"
10
11
  import { useAnimationFrame } from "../utils/hooks"
11
12
 
@@ -38,81 +39,83 @@ export type PoseJointValuesProps = {
38
39
  showCopyButton?: boolean
39
40
  }
40
41
 
41
- export const PoseJointValues = observer(
42
- ({
43
- motionStream,
44
- connectedMotionGroup,
45
- showCopyButton = false,
46
- }: PoseJointValuesProps) => {
47
- const poseHolderRef = useRef<HTMLDivElement>(null)
48
- const [copyMessage, setCopyMessage] = useState("")
49
-
50
- const activeMotionStream = createMotionStateProvider(
42
+ export const PoseJointValues = externalizeComponent(
43
+ observer(
44
+ ({
51
45
  motionStream,
52
46
  connectedMotionGroup,
53
- )
47
+ showCopyButton = false,
48
+ }: PoseJointValuesProps) => {
49
+ const poseHolderRef = useRef<HTMLDivElement>(null)
50
+ const [copyMessage, setCopyMessage] = useState("")
54
51
 
55
- if (!activeMotionStream) {
56
- throw new Error(
57
- "PoseJointValues requires either motionStream or connectedMotionGroup prop",
52
+ const activeMotionStream = createMotionStateProvider(
53
+ motionStream,
54
+ connectedMotionGroup,
58
55
  )
59
- }
60
-
61
- function getCurrentPoseString() {
62
- if (!activeMotionStream) return ""
63
- const { joints } =
64
- activeMotionStream.rapidlyChangingMotionState.state.joint_position
65
- return `[${joints.map((j: number) => parseFloat(j.toFixed(4))).join(", ")}]`
66
- }
67
56
 
68
- const handleCopy = async () => {
69
- try {
70
- await navigator.clipboard.writeText(getCurrentPoseString())
71
- setCopyMessage("Copied!")
72
- setTimeout(() => setCopyMessage(""), 2000)
73
- } catch {
74
- setCopyMessage("Copy failed")
75
- setTimeout(() => setCopyMessage(""), 2000)
57
+ if (!activeMotionStream) {
58
+ throw new Error(
59
+ "PoseJointValues requires either motionStream or connectedMotionGroup prop",
60
+ )
76
61
  }
77
- }
78
62
 
79
- useAnimationFrame(() => {
80
- if (!poseHolderRef.current) {
81
- return
63
+ function getCurrentPoseString() {
64
+ if (!activeMotionStream) return ""
65
+ const { joints } =
66
+ activeMotionStream.rapidlyChangingMotionState.state.joint_position
67
+ return `[${joints.map((j: number) => parseFloat(j.toFixed(4))).join(", ")}]`
82
68
  }
83
69
 
84
- const newPoseContent = getCurrentPoseString()
85
- if (poseHolderRef.current.textContent === newPoseContent) {
86
- return
70
+ const handleCopy = async () => {
71
+ try {
72
+ await navigator.clipboard.writeText(getCurrentPoseString())
73
+ setCopyMessage("Copied!")
74
+ setTimeout(() => setCopyMessage(""), 2000)
75
+ } catch {
76
+ setCopyMessage("Copy failed")
77
+ setTimeout(() => setCopyMessage(""), 2000)
78
+ }
87
79
  }
88
- poseHolderRef.current.textContent = newPoseContent
89
- })
90
80
 
91
- return (
92
- <Stack
93
- direction="row"
94
- alignItems="center"
95
- spacing={1}
96
- sx={{ flexGrow: 1, minWidth: 0, overflow: "hidden" }}
97
- >
98
- <CopyableText value={getCurrentPoseString()} ref={poseHolderRef} />
99
- {showCopyButton && (
100
- <Button
101
- variant="contained"
102
- color="secondary"
103
- size="small"
104
- onClick={handleCopy}
105
- sx={{ flexShrink: 0 }}
106
- >
107
- Copy
108
- </Button>
109
- )}
110
- {copyMessage && (
111
- <Typography variant="caption" color="success.main">
112
- {copyMessage}
113
- </Typography>
114
- )}
115
- </Stack>
116
- )
117
- },
81
+ useAnimationFrame(() => {
82
+ if (!poseHolderRef.current) {
83
+ return
84
+ }
85
+
86
+ const newPoseContent = getCurrentPoseString()
87
+ if (poseHolderRef.current.textContent === newPoseContent) {
88
+ return
89
+ }
90
+ poseHolderRef.current.textContent = newPoseContent
91
+ })
92
+
93
+ return (
94
+ <Stack
95
+ direction="row"
96
+ alignItems="center"
97
+ spacing={1}
98
+ sx={{ flexGrow: 1, minWidth: 0, overflow: "hidden" }}
99
+ >
100
+ <CopyableText value={getCurrentPoseString()} ref={poseHolderRef} />
101
+ {showCopyButton && (
102
+ <Button
103
+ variant="contained"
104
+ color="secondary"
105
+ size="small"
106
+ onClick={handleCopy}
107
+ sx={{ flexShrink: 0 }}
108
+ >
109
+ Copy
110
+ </Button>
111
+ )}
112
+ {copyMessage && (
113
+ <Typography variant="caption" color="success.main">
114
+ {copyMessage}
115
+ </Typography>
116
+ )}
117
+ </Stack>
118
+ )
119
+ },
120
+ ),
118
121
  )