@vention/machine-apps-components 0.3.18 → 0.3.20

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 CHANGED
@@ -95,10 +95,16 @@ const NAV_ITEMS = [
95
95
  A status bar component positioned at the top of the screen that displays:
96
96
 
97
97
  - Status indicator with label and colored dot
98
- - Configurable action buttons that can be shown/hidden
99
- - Button enable/disable states
98
+ - Configurable action buttons with visibility and disabled states
100
99
  - Custom styling for buttons
101
100
 
101
+ **Key Features:**
102
+
103
+ - ✅ Declarative, config-based API
104
+ - ✅ Fully controlled component (no internal state)
105
+ - ✅ Type-safe button configurations
106
+ - ✅ Easy to test and reason about
107
+
102
108
  #### Props
103
109
 
104
110
  ```typescript
@@ -106,6 +112,8 @@ interface ButtonConfig {
106
112
  id: string // Unique identifier for the button
107
113
  label: string // Button text
108
114
  onClick?: () => void // Click handler
115
+ visible?: boolean // Show/hide button (default: true)
116
+ disabled?: boolean // Enable/disable button (default: false)
109
117
  backgroundColor?: string // Button background color (e.g., "#4CAF50")
110
118
  backgroundColorHover?: string // Hover background color
111
119
  borderColor?: string // Border color
@@ -117,24 +125,11 @@ interface ButtonConfig {
117
125
  }
118
126
 
119
127
  interface StatusTopBarProps {
120
- statusLabel?: string // Label for the status indicator
121
- dotColor?: string // Color of the status dot
128
+ status?: {
129
+ label: string // Status label text
130
+ color: string // Color of the status dot
131
+ }
122
132
  buttonConfigs?: ButtonConfig[] // Array of button configurations
123
- visibleButtons?: string[] // Button IDs to show (controlled)
124
- disabledButtons?: string[] // Button IDs to disable (controlled)
125
- }
126
- ```
127
-
128
- #### API Methods (via ref)
129
-
130
- ```typescript
131
- interface StatusTopBarHandle {
132
- showButtons: (buttonIds: string[]) => void // Show specific buttons
133
- hideAllButtons: () => void // Hide all buttons
134
- disableButton: (buttonId: string) => void // Disable a specific button
135
- enableButton: (buttonId: string) => void // Enable a specific button
136
- disableAllButtons: () => void // Disable all buttons
137
- enableAllButtons: () => void // Enable all buttons
138
133
  }
139
134
  ```
140
135
 
@@ -143,137 +138,182 @@ interface StatusTopBarHandle {
143
138
  ##### Basic Usage
144
139
 
145
140
  ```tsx
146
- import { StatusTopBar } from "@vention/machine-apps-components"
147
- import { useRef } from "react"
148
- import type { StatusTopBarHandle } from "@vention/machine-apps-components"
149
-
150
- const BUTTON_CONFIGS = [
151
- {
152
- id: "enableFreeDrive",
153
- label: "Enable Free Drive",
154
- onClick: () => console.log("Free Drive enabled"),
155
- backgroundColor: "#4CAF50",
156
- backgroundColorHover: "#45A049",
157
- textColor: "#FFFFFF",
158
- },
159
- {
160
- id: "start",
161
- label: "Start",
162
- onClick: () => console.log("Start clicked"),
163
- backgroundColor: "#2196F3",
164
- textColor: "#FFFFFF",
165
- },
166
- {
167
- id: "stop",
168
- label: "Stop",
169
- onClick: () => console.log("Stop clicked"),
170
- backgroundColor: "#F44336",
171
- textColor: "#FFFFFF",
172
- },
173
- ]
141
+ import { StatusTopBar, ButtonConfig } from "@vention/machine-apps-components"
142
+ import { VentionIcon } from "@ventionco/machine-ui"
174
143
 
175
144
  function App() {
176
- const statusBarRef = useRef<StatusTopBarHandle>(null)
145
+ const buttonConfigs: ButtonConfig[] = [
146
+ {
147
+ id: "start",
148
+ label: "Start",
149
+ onClick: () => console.log("Start clicked"),
150
+ backgroundColor: "#2196F3",
151
+ textColor: "white",
152
+ icon: <VentionIcon size={32} type="player-play-filled" color="white" />,
153
+ visible: true,
154
+ disabled: false,
155
+ },
156
+ {
157
+ id: "stop",
158
+ label: "Stop",
159
+ onClick: () => console.log("Stop clicked"),
160
+ backgroundColor: "#F44336",
161
+ textColor: "white",
162
+ icon: <VentionIcon size={32} type="player-stop-filled" color="white" />,
163
+ visible: false, // Hidden by default
164
+ disabled: false,
165
+ },
166
+ ]
177
167
 
178
168
  return (
179
169
  <StatusTopBar
180
- ref={statusBarRef}
181
- statusLabel="Ready"
182
- dotColor="#4CAF50"
183
- buttonConfigs={BUTTON_CONFIGS}
184
- visibleButtons={["start"]}
170
+ status={{
171
+ label: "Machine state: Ready",
172
+ color: "#4CAF50",
173
+ }}
174
+ buttonConfigs={buttonConfigs}
185
175
  />
186
176
  )
187
177
  }
188
178
  ```
189
179
 
190
- ##### Controlling Buttons Programmatically
191
-
192
- ```tsx
193
- const statusBarRef = useRef<StatusTopBarHandle>(null)
180
+ ##### Dynamic State Management
194
181
 
195
- // Show specific buttons
196
- statusBarRef.current?.showButtons(["enableFreeDrive", "start"])
182
+ The component is fully controlled - all button states are derived from your application state:
197
183
 
198
- // Hide all buttons
199
- statusBarRef.current?.hideAllButtons()
184
+ ```tsx
185
+ import { StatusTopBar, ButtonConfig } from "@vention/machine-apps-components"
186
+ import { useMemo } from "react"
187
+
188
+ function MachineApp() {
189
+ const { machineState, isRunning, canStart } = useMachineState()
190
+
191
+ const buttonConfigs: ButtonConfig[] = useMemo(() => {
192
+ const configs: ButtonConfig[] = []
193
+
194
+ // Start button - visible when not running
195
+ if (!isRunning) {
196
+ configs.push({
197
+ id: "start",
198
+ label: "Start",
199
+ onClick: handleStart,
200
+ backgroundColor: "#2196F3",
201
+ textColor: "white",
202
+ visible: true,
203
+ disabled: !canStart, // Disabled if prerequisites not met
204
+ })
205
+ }
200
206
 
201
- // Disable a specific button
202
- statusBarRef.current?.disableButton("start")
207
+ // Stop button - visible when running
208
+ if (isRunning) {
209
+ configs.push({
210
+ id: "stop",
211
+ label: "Stop",
212
+ onClick: handleStop,
213
+ backgroundColor: "#F44336",
214
+ textColor: "white",
215
+ visible: true,
216
+ disabled: false,
217
+ })
218
+ }
203
219
 
204
- // Enable a specific button
205
- statusBarRef.current?.enableButton("start")
220
+ // Home button - always visible
221
+ configs.push({
222
+ id: "home",
223
+ label: "Home",
224
+ onClick: handleHome,
225
+ borderColor: "#E2E8F0",
226
+ textColor: "#1A202C",
227
+ visible: true,
228
+ disabled: isRunning, // Can't home while running
229
+ })
206
230
 
207
- // Disable all buttons
208
- statusBarRef.current?.disableAllButtons()
231
+ return configs
232
+ }, [isRunning, canStart])
209
233
 
210
- // Enable all buttons
211
- statusBarRef.current?.enableAllButtons()
234
+ return (
235
+ <StatusTopBar
236
+ status={{
237
+ label: `Machine state: ${machineState}`,
238
+ color: getStatusColor(machineState),
239
+ }}
240
+ buttonConfigs={buttonConfigs}
241
+ />
242
+ )
243
+ }
212
244
  ```
213
245
 
214
- #### Console Testing Commands
215
-
216
- For development and debugging, the StatusTopBar component exposes its API methods on the `window` object. You can test button visibility and states directly from the browser console:
217
-
218
- ##### StatusTopBar Commands
219
-
220
- ```javascript
221
- // Show specific buttons (pass an array of button IDs)
222
- window.showButtons(["enableFreeDrive", "start"])
223
-
224
- // Show a single button
225
- window.showButtons(["enableFreeDrive"])
226
-
227
- // Hide all buttons
228
- window.hideAllButtons()
246
+ ##### Conditional Buttons
229
247
 
230
- // Disable a specific button
231
- window.disableButton("start")
248
+ Easily show/hide buttons based on application state:
232
249
 
233
- // Enable a specific button
234
- window.enableButton("start")
250
+ ```tsx
251
+ const buttonConfigs: ButtonConfig[] = useMemo(() => {
252
+ const configs: ButtonConfig[] = []
253
+
254
+ // Operation page buttons
255
+ if (currentPage === "operation") {
256
+ configs.push({
257
+ id: "start",
258
+ label: isRunning ? "Stop" : "Start",
259
+ onClick: isRunning ? handleStop : handleStart,
260
+ backgroundColor: isRunning ? "#F44336" : "#2196F3",
261
+ textColor: "white",
262
+ visible: true,
263
+ disabled: false,
264
+ })
265
+ }
235
266
 
236
- // Disable all buttons
237
- window.disableAllButtons()
267
+ // Calibration page buttons
268
+ if (currentPage === "calibration") {
269
+ configs.push({
270
+ id: "freedrive",
271
+ label: isFreeDriveEnabled ? "Disable Free Drive" : "Enable Free Drive",
272
+ onClick: toggleFreeDrive,
273
+ backgroundColor: isFreeDriveEnabled ? "#CBD5E0" : undefined,
274
+ borderColor: !isFreeDriveEnabled ? "#E2E8F0" : undefined,
275
+ textColor: "black",
276
+ visible: true,
277
+ disabled: false,
278
+ })
279
+ }
238
280
 
239
- // Enable all buttons
240
- window.enableAllButtons()
281
+ return configs
282
+ }, [currentPage, isRunning, isFreeDriveEnabled])
241
283
  ```
242
284
 
243
- **Important:** The `showButtons` function requires an **array** of button IDs, not a single string. This is a common mistake when testing in the console.
285
+ #### Migration from Imperative API
244
286
 
245
- **Correct:**
287
+ If you're migrating from the old compound component pattern:
246
288
 
247
- ```javascript
248
- window.showButtons(["enableFreeDrive"])
249
- window.showButtons(["enableFreeDrive", "start", "stop"])
250
- ```
251
-
252
- ❌ **Incorrect:**
289
+ **Before (compound components):**
253
290
 
254
- ```javascript
255
- window.showButtons("enableFreeDrive") // Will cause "visibleButtons.map is not a function" error
291
+ ```tsx
292
+ <StatusTopBar statusLabel="Ready" dotColor="#4CAF50" visibleButtons={["start"]} disabledButtons={["stop"]}>
293
+ <StatusTopBar.Button id="start" label="Start" onClick={handleStart} backgroundColor="#2196F3" textColor="white" />
294
+ <StatusTopBar.Button id="stop" label="Stop" onClick={handleStop} backgroundColor="#F44336" textColor="white" />
295
+ </StatusTopBar>
256
296
  ```
257
297
 
258
- ##### NavigationBar Console Testing
298
+ **After (config-based):**
259
299
 
260
- The NavigationBar doesn't expose window commands because it's controlled by React Router. To test navigation:
261
-
262
- ```javascript
263
- // In browser console, navigate programmatically
264
- window.location.hash = "#/operation"
265
- window.location.hash = "#/settings"
266
- window.location.hash = "#/control-center"
300
+ ```tsx
301
+ <StatusTopBar
302
+ status={{ label: "Ready", color: "#4CAF50" }}
303
+ buttonConfigs={[
304
+ { id: "start", label: "Start", onClick: handleStart, backgroundColor: "#2196F3", textColor: "white", visible: true },
305
+ { id: "stop", label: "Stop", onClick: handleStop, backgroundColor: "#F44336", textColor: "white", visible: false },
306
+ ]}
307
+ />
267
308
  ```
268
309
 
269
- Or use React Router's imperative navigation in your component:
310
+ **Benefits of the new API:**
270
311
 
271
- ```tsx
272
- import { useNavigate } from "react-router-dom"
273
-
274
- const navigate = useNavigate()
275
- navigate("/operation")
276
- ```
312
+ - Single source of truth for button state
313
+ - No need for refs or imperative methods
314
+ - Easier to test (just pass different configs)
315
+ - Better TypeScript support
316
+ - More predictable behavior
277
317
 
278
318
  ### Logs
279
319
 
package/index.esm.js CHANGED
@@ -668,7 +668,7 @@ const StatusTopBarButton = props => {
668
668
  icon,
669
669
  iconDisabled,
670
670
  visible = true,
671
- isDisabled = false
671
+ disabled = false
672
672
  } = props;
673
673
  const {
674
674
  classes
@@ -679,20 +679,20 @@ const StatusTopBarButton = props => {
679
679
  };
680
680
  const hasBackgroundColor = !!backgroundColor;
681
681
  const variant = hasBackgroundColor ? "filled-brand" : "outline";
682
- const finalBackgroundColor = isDisabled && backgroundColor ? getFadedColor(backgroundColor) : backgroundColor;
683
- const finalBackgroundColorHover = isDisabled && backgroundColorHover ? getFadedColor(backgroundColorHover) : backgroundColorHover;
684
- const finalBorderColor = isDisabled && borderColor ? getFadedColor(borderColor) : borderColor;
685
- const finalTextColor = isDisabled && textColor ? getFadedColor(textColor) : textColor;
686
- const finalIcon = isDisabled && iconDisabled ? iconDisabled : icon;
682
+ const finalBackgroundColor = disabled && backgroundColor ? getFadedColor(backgroundColor) : backgroundColor;
683
+ const finalBackgroundColorHover = disabled && backgroundColorHover ? getFadedColor(backgroundColorHover) : backgroundColorHover;
684
+ const finalBorderColor = disabled && borderColor ? getFadedColor(borderColor) : borderColor;
685
+ const finalTextColor = disabled && textColor ? getFadedColor(textColor) : textColor;
686
+ const finalIcon = disabled && iconDisabled ? iconDisabled : icon;
687
687
  const handleClick = () => {
688
- if (!isDisabled && onClick) {
688
+ if (!disabled && onClick) {
689
689
  onClick();
690
690
  }
691
691
  };
692
692
  return jsx(VentionButton, {
693
693
  variant: variant,
694
694
  onClick: handleClick,
695
- disabled: isDisabled || !onClick,
695
+ disabled: disabled || !onClick,
696
696
  className: classes.actionButton,
697
697
  style: {
698
698
  width,
@@ -748,53 +748,30 @@ const useStyles$6 = tss.create(({
748
748
 
749
749
  const StatusTopBarRoot = props => {
750
750
  const {
751
- children,
752
- statusLabel,
753
- dotColor,
754
- visibleButtons,
755
- disabledButtons = []
751
+ status,
752
+ buttonConfigs = []
756
753
  } = props;
757
754
  const {
758
755
  classes
759
756
  } = useStyles$5();
760
- const visibleSet = visibleButtons ? new Set(visibleButtons) : null;
761
- const disabledSet = new Set(disabledButtons);
762
- const processedChildren = React.Children.map(children, child => {
763
- if (!React.isValidElement(child)) return child;
764
- if (child.type !== StatusTopBarButton) return child;
765
- const buttonId = child.props.id;
766
- const visible = visibleSet === null || visibleSet.has(buttonId);
767
- const isDisabled = disabledSet.has(buttonId);
768
- return React.cloneElement(child, Object.assign(Object.assign({}, child.props), {
769
- visible: visible,
770
- isDisabled: isDisabled
771
- }));
772
- });
773
- const hasVisibleButtons = React.Children.toArray(children).some(child => {
774
- if (!React.isValidElement(child) || child.type !== StatusTopBarButton) return false;
775
- const buttonId = child.props.id;
776
- const visible = visibleSet === null || visibleSet.has(buttonId);
777
- return visible;
778
- });
757
+ const visibleButtons = buttonConfigs.filter(config => config.visible !== false);
758
+ const hasVisibleButtons = visibleButtons.length > 0;
779
759
  return jsxs("header", {
780
760
  className: classes.root,
781
761
  children: [jsxs("div", {
782
762
  className: classes.content,
783
763
  children: [jsx("div", {
784
764
  className: classes.leftArea,
785
- children: jsx(VentionStatusIndicator, {
765
+ children: status && jsx(VentionStatusIndicator, {
786
766
  size: "xx-large",
787
- dotColor: dotColor,
788
- label: statusLabel
767
+ dotColor: status.color,
768
+ label: status.label
789
769
  })
790
770
  }), hasVisibleButtons && jsx("div", {
791
771
  className: classes.rightArea,
792
- children: processedChildren
793
- }), !hasVisibleButtons && jsx("div", {
794
- style: {
795
- display: "none"
796
- },
797
- children: processedChildren
772
+ children: visibleButtons.map(config => jsx(StatusTopBarButton, Object.assign({}, config, {
773
+ visible: true
774
+ }), config.id))
798
775
  })]
799
776
  }), jsx("div", {
800
777
  className: classes.bottomBorder
@@ -838,9 +815,7 @@ const useStyles$5 = tss.create(({
838
815
  backgroundColor: theme.palette.divider
839
816
  }
840
817
  }));
841
- const StatusTopBar = Object.assign(StatusTopBarRoot, {
842
- Button: StatusTopBarButton
843
- });
818
+ const StatusTopBar = StatusTopBarRoot;
844
819
 
845
820
  const Sidebar = props => {
846
821
  const {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vention/machine-apps-components",
3
- "version": "0.3.18",
3
+ "version": "0.3.20",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/VentionCo/machine-cloud.git"
package/src/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export * from "./lib/navigation-bar/navigation-confirmation-modal";
3
3
  export * from "./lib/navigation-bar/password-protection-modal";
4
4
  export * from "./lib/navigation-bar/time-label";
5
5
  export * from "./lib/status-top-bar/status-top-bar";
6
+ export type { ButtonConfig, StatusTopBarProps } from "./lib/status-top-bar/status-top-bar";
6
7
  export * from "./lib/sidebar/sidebar";
7
8
  export type { SidebarItem, SidebarProps } from "./lib/sidebar/sidebar";
8
9
  export * from "./lib/logs/logs-table";
@@ -11,7 +11,7 @@ export interface StatusTopBarButtonProps {
11
11
  height?: number;
12
12
  icon?: ReactNode;
13
13
  iconDisabled?: ReactNode;
14
- isDisabled?: boolean;
14
+ disabled?: boolean;
15
15
  visible?: boolean;
16
16
  }
17
17
  export declare const StatusTopBarButton: (props: StatusTopBarButtonProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,13 +1,25 @@
1
1
  import { ReactNode } from "react";
2
- import { StatusTopBarButtonProps } from "./status-top-bar-button";
2
+ export interface ButtonConfig {
3
+ id: string;
4
+ label: string;
5
+ onClick?: () => void;
6
+ visible?: boolean;
7
+ disabled?: boolean;
8
+ backgroundColor?: string;
9
+ backgroundColorHover?: string;
10
+ borderColor?: string;
11
+ textColor?: string;
12
+ width?: number;
13
+ height?: number;
14
+ icon?: ReactNode;
15
+ iconDisabled?: ReactNode;
16
+ }
3
17
  export interface StatusTopBarProps {
4
- statusLabel?: string;
5
- dotColor?: string;
6
- children?: ReactNode;
7
- visibleButtons?: string[];
8
- disabledButtons?: string[];
18
+ status?: {
19
+ label: string;
20
+ color: string;
21
+ };
22
+ buttonConfigs?: ButtonConfig[];
9
23
  }
10
- export declare const StatusTopBar: ((props: StatusTopBarProps) => import("react/jsx-runtime").JSX.Element) & {
11
- Button: (props: StatusTopBarButtonProps) => import("react/jsx-runtime").JSX.Element;
12
- };
24
+ export declare const StatusTopBar: (props: StatusTopBarProps) => import("react/jsx-runtime").JSX.Element;
13
25
  export default StatusTopBar;