@wandelbots/wandelbots-js-react-components 2.32.0 → 2.33.0-pr.feature-robot-precondition-list.372.3ad6c62

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 (45) hide show
  1. package/dist/components/CycleTimer.d.ts.map +1 -1
  2. package/dist/components/DataGrid.d.ts +61 -0
  3. package/dist/components/DataGrid.d.ts.map +1 -0
  4. package/dist/components/LogPanel.d.ts +38 -0
  5. package/dist/components/LogPanel.d.ts.map +1 -0
  6. package/dist/components/LogStore.d.ts +11 -0
  7. package/dist/components/LogStore.d.ts.map +1 -0
  8. package/dist/components/LogViewer.d.ts +26 -0
  9. package/dist/components/LogViewer.d.ts.map +1 -0
  10. package/dist/components/ProgramControl.d.ts +6 -1
  11. package/dist/components/ProgramControl.d.ts.map +1 -1
  12. package/dist/components/ProgramStateIndicator.d.ts +1 -1
  13. package/dist/components/ProgramStateIndicator.d.ts.map +1 -1
  14. package/dist/components/RobotCard.d.ts +84 -0
  15. package/dist/components/RobotCard.d.ts.map +1 -0
  16. package/dist/components/RobotListItem.d.ts +34 -0
  17. package/dist/components/RobotListItem.d.ts.map +1 -0
  18. package/dist/components/RobotSetupReadinessIndicator.d.ts +31 -0
  19. package/dist/components/RobotSetupReadinessIndicator.d.ts.map +1 -0
  20. package/dist/components/RobotSetupReadinessIndicator.test.d.ts +2 -0
  21. package/dist/components/RobotSetupReadinessIndicator.test.d.ts.map +1 -0
  22. package/dist/components/robots/Robot.d.ts +3 -2
  23. package/dist/components/robots/Robot.d.ts.map +1 -1
  24. package/dist/index.cjs +49 -49
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.ts +7 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +8288 -7119
  29. package/dist/index.js.map +1 -1
  30. package/package.json +2 -1
  31. package/src/components/CycleTimer.tsx +43 -64
  32. package/src/components/DataGrid.tsx +454 -0
  33. package/src/components/LogPanel.tsx +69 -0
  34. package/src/components/LogStore.ts +40 -0
  35. package/src/components/LogViewer.tsx +311 -0
  36. package/src/components/ProgramControl.tsx +20 -10
  37. package/src/components/ProgramStateIndicator.tsx +20 -8
  38. package/src/components/RobotCard.tsx +576 -0
  39. package/src/components/RobotListItem.tsx +152 -0
  40. package/src/components/RobotSetupReadinessIndicator.test.tsx +60 -0
  41. package/src/components/RobotSetupReadinessIndicator.tsx +124 -0
  42. package/src/components/robots/Robot.tsx +5 -2
  43. package/src/i18n/locales/de/translations.json +6 -1
  44. package/src/i18n/locales/en/translations.json +6 -1
  45. package/src/index.ts +7 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wandelbots/wandelbots-js-react-components",
3
- "version": "2.32.0",
3
+ "version": "2.33.0-pr.feature-robot-precondition-list.372.3ad6c62",
4
4
  "description": "React UI toolkit for building applications on top of the Wandelbots platform",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -139,6 +139,7 @@
139
139
  "dependencies": {
140
140
  "@monaco-editor/react": "^4.7.0",
141
141
  "@mui/x-charts": "^8.9.0",
142
+ "@mui/x-data-grid": "^8.10.1",
142
143
  "@shikijs/monaco": "^3.1.0",
143
144
  "i18next-browser-languagedetector": "^8.0.4",
144
145
  "lodash-es": "^4.17.21",
@@ -301,77 +301,56 @@ export const CycleTimer = externalizeComponent(
301
301
  sx={{
302
302
  display: "flex",
303
303
  alignItems: "center",
304
- gap: 0.125, // Minimal gap - 1px
304
+ m: 0,
305
+ gap: 1, // 8px gap between circle and text
305
306
  }}
306
307
  >
307
- {/* Animated progress gauge icon */}
308
+ {/* Animated progress ring icon */}
308
309
  <Box
309
310
  sx={{
310
- position: "relative",
311
- width: 40,
312
- height: 40,
311
+ width: 20,
312
+ height: 20,
313
313
  display: "flex",
314
314
  alignItems: "center",
315
315
  justifyContent: "center",
316
- borderRadius: "50%",
317
- overflow: "visible",
316
+ opacity: isPausedState ? 0.6 : 1,
317
+ transition: "opacity 0.2s ease",
318
318
  }}
319
319
  >
320
- <Gauge
321
- width={40}
322
- height={40}
323
- value={progressValue}
324
- valueMin={0}
325
- valueMax={100}
326
- innerRadius="70%"
327
- outerRadius="95%"
328
- skipAnimation={true}
329
- sx={{
330
- opacity: isPausedState ? 0.6 : 1,
331
- transition: "opacity 0.2s ease",
332
- [`& .MuiGauge-valueArc`]: {
333
- fill: theme.palette.success.main,
334
- },
335
- [`& .MuiGauge-referenceArc`]: {
336
- fill: theme.palette.success.main,
337
- opacity: 0.3,
338
- },
339
- [`& .MuiGauge-valueText`]: {
340
- display: "none",
341
- },
342
- [`& .MuiGauge-text`]: {
343
- display: "none",
344
- },
345
- [`& text`]: {
346
- display: "none",
347
- },
348
- // Hide any inner circle elements that might flash
349
- [`& .MuiGauge-referenceArcBackground`]: {
350
- display: "none",
351
- },
352
- [`& .MuiGauge-valueArcBackground`]: {
353
- display: "none",
354
- },
355
- [`& circle`]: {
356
- display: "none",
357
- },
358
- }}
359
- />
360
-
361
- {/* Inner circle overlay to prevent flashing */}
362
- <Box
363
- sx={{
364
- position: "absolute",
365
- top: "50%",
366
- left: "50%",
367
- transform: "translate(-50%, -50%)",
368
- width: 13,
369
- height: 13,
370
- borderRadius: "50%",
371
- backgroundColor: theme.palette.background?.paper || "white",
372
- pointerEvents: "none",
373
- }}
374
- />
320
+ <svg
321
+ width="20"
322
+ height="20"
323
+ viewBox="0 0 20 20"
324
+ style={{ transform: "rotate(-90deg)" }}
325
+ role="img"
326
+ aria-label="Timer progress"
327
+ >
328
+ {/* Background ring */}
329
+ <circle
330
+ cx="10"
331
+ cy="10"
332
+ r="8"
333
+ fill="none"
334
+ stroke={theme.palette.success.main}
335
+ strokeWidth="2"
336
+ opacity={0.3}
337
+ />
338
+ {/* Progress ring */}
339
+ <circle
340
+ cx="10"
341
+ cy="10"
342
+ r="8"
343
+ fill="none"
344
+ stroke={theme.palette.success.main}
345
+ strokeWidth="2"
346
+ strokeLinecap="round"
347
+ strokeDasharray={`${2 * Math.PI * 8}`}
348
+ strokeDashoffset={`${2 * Math.PI * 8 * (1 - progressValue / 100)}`}
349
+ style={{
350
+ transition: "stroke-dashoffset 0.1s ease-out",
351
+ }}
352
+ />
353
+ </svg>
375
354
  </Box>
376
355
 
377
356
  {/* Timer text display */}
@@ -383,8 +362,8 @@ export const CycleTimer = externalizeComponent(
383
362
  }}
384
363
  >
385
364
  {compact
386
- ? // Compact mode: show only remaining time
387
- formatTime(remainingTime)
365
+ ? // Compact mode: show remaining time with "min." suffix
366
+ `${formatTime(remainingTime)} ${t("CycleTimer.Time.lb", { time: "" }).replace(/\s*$/, "")}`
388
367
  : // Full mode: show "remaining / of total min." format
389
368
  `${formatTime(remainingTime)} / ${t("CycleTimer.Time.lb", { time: formatTime(maxTime) })}`}
390
369
  </Typography>
@@ -0,0 +1,454 @@
1
+ import ClearIcon from "@mui/icons-material/Clear"
2
+ import FilterListIcon from "@mui/icons-material/FilterList"
3
+ import SearchIcon from "@mui/icons-material/Search"
4
+ import { Box, Divider, Typography } from "@mui/material"
5
+ import {
6
+ DataGrid,
7
+ type DataGridProps,
8
+ FilterPanelTrigger,
9
+ type GridColDef,
10
+ type GridRowParams,
11
+ QuickFilter,
12
+ QuickFilterClear,
13
+ QuickFilterControl,
14
+ QuickFilterTrigger,
15
+ Toolbar,
16
+ ToolbarButton,
17
+ } from "@mui/x-data-grid"
18
+ import { observer } from "mobx-react-lite"
19
+ import { useMemo, useState } from "react"
20
+ import { externalizeComponent } from "../externalizeComponent"
21
+
22
+ export interface WandelbotsDataGridProps<T = Record<string, unknown>> {
23
+ /**
24
+ * Array of data items to display in the grid
25
+ */
26
+ data: T[]
27
+
28
+ /**
29
+ * Column definitions for the DataGrid
30
+ */
31
+ columns: GridColDef[]
32
+
33
+ /**
34
+ * Function to transform data items into DataGrid rows
35
+ * Should return an object with an 'id' field and other fields matching column definitions
36
+ */
37
+ getRowData: (item: T) => Record<string, unknown> & { id: string | number }
38
+
39
+ /**
40
+ * Callback when a row is clicked
41
+ */
42
+ onRowClick?: (item: T, params: GridRowParams) => void
43
+
44
+ /**
45
+ * Currently selected item (for highlighting)
46
+ */
47
+ selectedItem?: T | null
48
+
49
+ /**
50
+ * Function to get the ID of an item (used for selection highlighting)
51
+ */
52
+ getItemId?: (item: T) => string | number
53
+
54
+ /**
55
+ * Title displayed in the toolbar
56
+ */
57
+ title?: string
58
+
59
+ /**
60
+ * Show item count in title
61
+ * @default true
62
+ */
63
+ showCount?: boolean
64
+
65
+ /**
66
+ * Placeholder text for the search input
67
+ * @default "Search"
68
+ */
69
+ searchPlaceholder?: string
70
+
71
+ /**
72
+ * Additional DataGrid props to pass through
73
+ */
74
+ dataGridProps?: Partial<DataGridProps>
75
+
76
+ /**
77
+ * Custom toolbar component to replace the default one
78
+ */
79
+ CustomToolbar?: React.ComponentType
80
+
81
+ /**
82
+ * Select the first item by default
83
+ * @default false
84
+ */
85
+ selectFirstByDefault?: boolean
86
+ }
87
+
88
+ export const WandelbotsDataGrid = externalizeComponent(
89
+ observer(
90
+ <T,>({
91
+ data,
92
+ columns,
93
+ getRowData,
94
+ onRowClick,
95
+ selectedItem,
96
+ getItemId,
97
+ title,
98
+ showCount = true,
99
+ searchPlaceholder = "Search",
100
+ dataGridProps,
101
+ CustomToolbar,
102
+ selectFirstByDefault = false,
103
+ }: WandelbotsDataGridProps<T>) => {
104
+ // Internal state for selection when not controlled
105
+ const [internalSelectedItem, setInternalSelectedItem] =
106
+ useState<T | null>(null)
107
+
108
+ // Prepare rows for the DataGrid
109
+ const rows = useMemo(() => data.map(getRowData), [data, getRowData])
110
+
111
+ // Handle default selection - only use if no selectedItem is explicitly provided
112
+ const effectiveSelectedItem = useMemo(() => {
113
+ // If selectedItem is explicitly provided, use it (including null)
114
+ if (selectedItem !== undefined) {
115
+ return selectedItem
116
+ }
117
+ // If we have an internal selection, use it
118
+ if (internalSelectedItem !== null) {
119
+ return internalSelectedItem
120
+ }
121
+ // Otherwise, use first item if selectFirstByDefault is true
122
+ if (selectFirstByDefault && data.length > 0) {
123
+ const firstItem = data[0]
124
+ // Set internal state to first item on initial load
125
+ setInternalSelectedItem(firstItem)
126
+ return firstItem
127
+ }
128
+ return null
129
+ }, [selectFirstByDefault, data, selectedItem, internalSelectedItem])
130
+
131
+ // Handle row click
132
+ const handleRowClick = (params: GridRowParams) => {
133
+ const item = data.find((item) => {
134
+ const rowData = getRowData(item)
135
+ return rowData.id === params.id
136
+ })
137
+
138
+ if (item) {
139
+ // Update internal selection state if not controlled by props
140
+ if (selectedItem === undefined) {
141
+ setInternalSelectedItem(item)
142
+ }
143
+
144
+ // Call the user's onRowClick callback
145
+ if (onRowClick) {
146
+ onRowClick(item, params)
147
+ }
148
+ }
149
+ }
150
+
151
+ // Get selected row ID for highlighting
152
+ const selectedRowId = useMemo(() => {
153
+ if (!effectiveSelectedItem || !getItemId) return null
154
+ return getItemId(effectiveSelectedItem)
155
+ }, [effectiveSelectedItem, getItemId])
156
+
157
+ // Default toolbar with filter and quick filter
158
+ function DefaultToolbar() {
159
+ return (
160
+ <Toolbar>
161
+ <Box
162
+ sx={{
163
+ display: "flex",
164
+ width: "100%",
165
+ gap: 1,
166
+ p: 1,
167
+ alignItems: "center",
168
+ }}
169
+ >
170
+ {title && (
171
+ <Typography
172
+ variant="h6"
173
+ sx={{
174
+ fontWeight: 500,
175
+ color: "white",
176
+ }}
177
+ >
178
+ {title}
179
+ {showCount && ` (${data.length})`}
180
+ </Typography>
181
+ )}
182
+ <Box
183
+ sx={{
184
+ ml: "auto",
185
+ display: "flex",
186
+ gap: 1,
187
+ alignItems: "center",
188
+ }}
189
+ >
190
+ <FilterPanelTrigger
191
+ render={
192
+ <ToolbarButton aria-label="Show filters">
193
+ <FilterListIcon fontSize="small" />
194
+ </ToolbarButton>
195
+ }
196
+ />
197
+ <Divider
198
+ orientation="vertical"
199
+ flexItem
200
+ sx={{
201
+ height: "24px",
202
+ alignSelf: "center",
203
+ }}
204
+ />
205
+ <QuickFilter
206
+ render={(props, state) => (
207
+ <Box
208
+ {...props}
209
+ sx={{
210
+ display: "flex",
211
+ overflow: "hidden",
212
+ }}
213
+ >
214
+ <Box
215
+ sx={{
216
+ borderRadius: state.expanded ? "4px 0 0 4px" : "4px",
217
+ borderRight: state.expanded ? "none" : undefined,
218
+ }}
219
+ >
220
+ <QuickFilterTrigger
221
+ render={
222
+ <ToolbarButton aria-label="Search">
223
+ <SearchIcon fontSize="small" />
224
+ </ToolbarButton>
225
+ }
226
+ />
227
+ </Box>
228
+ <Box
229
+ sx={{
230
+ display: "flex",
231
+ overflow: "hidden",
232
+ transition: "all 0.3s ease-in-out",
233
+ width: state.expanded ? "192px" : "0px",
234
+ }}
235
+ >
236
+ <Box
237
+ sx={{
238
+ flex: 1,
239
+ "& .MuiInputBase-root": {
240
+ height: "32px",
241
+ borderRadius:
242
+ state.expanded && state.value !== ""
243
+ ? "0"
244
+ : "0 4px 4px 0",
245
+ },
246
+ }}
247
+ >
248
+ <QuickFilterControl placeholder={searchPlaceholder} />
249
+ </Box>
250
+ {state.expanded && state.value !== "" && (
251
+ <QuickFilterClear
252
+ render={
253
+ <Box sx={{ borderRadius: "0 4px 4px 0" }}>
254
+ <ToolbarButton aria-label="Clear">
255
+ <ClearIcon fontSize="small" />
256
+ </ToolbarButton>
257
+ </Box>
258
+ }
259
+ />
260
+ )}
261
+ </Box>
262
+ </Box>
263
+ )}
264
+ />
265
+ </Box>
266
+ </Box>
267
+ </Toolbar>
268
+ )
269
+ }
270
+
271
+ const ToolbarComponent = CustomToolbar || DefaultToolbar
272
+
273
+ return (
274
+ <Box
275
+ sx={{
276
+ height: "100%",
277
+ display: "flex",
278
+ flexDirection: "column",
279
+ background: "var(--background-paper-elevation-5, #202233)",
280
+ }}
281
+ >
282
+ <DataGrid
283
+ rows={rows}
284
+ columns={columns}
285
+ onRowClick={handleRowClick}
286
+ disableColumnMenu={false}
287
+ disableRowSelectionOnClick={true}
288
+ disableMultipleRowSelection={true}
289
+ hideFooterSelectedRowCount={true}
290
+ filterMode="client"
291
+ sortingOrder={["desc", "asc"]}
292
+ hideFooter={false}
293
+ showToolbar={true}
294
+ slots={{
295
+ toolbar: ToolbarComponent,
296
+ }}
297
+ initialState={{
298
+ sorting: {
299
+ sortModel: [],
300
+ },
301
+ filter: {
302
+ filterModel: {
303
+ items: [],
304
+ },
305
+ },
306
+ ...dataGridProps?.initialState,
307
+ }}
308
+ {...dataGridProps}
309
+ // Ensure autosize properties are not overridden by dataGridProps
310
+ autosizeOnMount={dataGridProps?.autosizeOnMount ?? true}
311
+ autosizeOptions={{
312
+ includeOutliers: true,
313
+ includeHeaders: true,
314
+ expand: true,
315
+ ...dataGridProps?.autosizeOptions,
316
+ }}
317
+ sx={{
318
+ border: "none",
319
+ backgroundColor: "transparent",
320
+ width: "100%",
321
+ "& .MuiDataGrid-main": {
322
+ border: "none",
323
+ backgroundColor: "transparent",
324
+ },
325
+ "& .MuiDataGrid-container--top [role=row]": {
326
+ backgroundColor: "transparent !important",
327
+ },
328
+ "& .MuiDataGrid-topContainer": {
329
+ borderBottom: "none !important",
330
+ },
331
+ "& .MuiDataGrid-columnHeaders": {
332
+ border: "none",
333
+ borderBottom: "none !important",
334
+ backgroundColor: "var(--background-paper-elevation-5, #202233)",
335
+ },
336
+ "& .MuiDataGrid-row": {
337
+ cursor: onRowClick ? "pointer" : "default",
338
+ border: "none",
339
+ margin: "0px 0",
340
+ position: "relative",
341
+ "&:hover": {
342
+ backgroundColor: "transparent !important",
343
+ "&::before": {
344
+ content: '""',
345
+ position: "absolute",
346
+ top: 0,
347
+ left: "16px",
348
+ right: "16px",
349
+ bottom: 0,
350
+ backgroundColor: "action.hover",
351
+ borderRadius: "16px",
352
+ zIndex: 0,
353
+ },
354
+ },
355
+ // Disable MUI's built-in selection styling completely
356
+ "&.Mui-selected": {
357
+ backgroundColor: "transparent !important",
358
+ "&:hover": {
359
+ backgroundColor: "transparent !important",
360
+ },
361
+ },
362
+ // Highlight selected row with a distinct color using data attribute
363
+ ...(selectedRowId !== null && {
364
+ [`&[data-id="${selectedRowId}"]`]: {
365
+ "&::before": {
366
+ content: '""',
367
+ position: "absolute",
368
+ top: 0,
369
+ left: "16px",
370
+ right: "16px",
371
+ bottom: 0,
372
+ backgroundColor: "rgba(255, 255, 255, 0.08) !important",
373
+ borderRadius: "16px",
374
+ zIndex: 0,
375
+ },
376
+ "&:hover::before": {
377
+ backgroundColor: "rgba(255, 255, 255, 0.12) !important",
378
+ },
379
+ },
380
+ }),
381
+ },
382
+ "& .MuiDataGrid-cell--textLeft": {
383
+ paddingLeft: "40px",
384
+ },
385
+ "& .MuiDataGrid-cell": {
386
+ border: "none",
387
+ position: "relative",
388
+ zIndex: 1,
389
+ "&:focus": {
390
+ outline: "none",
391
+ },
392
+ "&:focus-within": {
393
+ outline: "none",
394
+ },
395
+ },
396
+ "& .MuiDataGrid-columnHeader": {
397
+ border: "none",
398
+ paddingLeft: "40px",
399
+ paddingRight: "40px",
400
+ backgroundColor: "transparent !important",
401
+ "& .MuiDataGrid-columnHeaderTitle": {
402
+ color: "rgba(255, 255, 255, 0.6)",
403
+ },
404
+ },
405
+ "& .MuiDataGrid-toolbarContainer": {
406
+ padding: "8px",
407
+ border: "none !important",
408
+ borderBottom: "none !important",
409
+ "& .MuiBox-root": {
410
+ backgroundColor: "transparent !important",
411
+ },
412
+ "& .MuiFormControl-root": {
413
+ backgroundColor: "transparent !important",
414
+ },
415
+ "& .MuiInputBase-root": {
416
+ backgroundColor: "transparent !important",
417
+ },
418
+ "& *": {
419
+ borderBottom: "none !important",
420
+ },
421
+ },
422
+ "& .MuiDataGrid-toolbar": {
423
+ borderBottom: "none !important",
424
+ },
425
+ "& .MuiDataGrid-toolbarFilterList": {
426
+ border: "none",
427
+ },
428
+ "& .MuiDataGrid-withBorderColor": {
429
+ borderColor: "transparent !important",
430
+ },
431
+ "& .MuiDataGrid-columnSeparator": {
432
+ display: "none",
433
+ },
434
+ "& .MuiDataGrid-footerContainer": {
435
+ display: "none",
436
+ },
437
+ "& .MuiDataGrid-filler": {
438
+ border: "none !important",
439
+ borderTop: "none !important",
440
+ borderBottom: "none !important",
441
+ borderLeft: "none !important",
442
+ borderRight: "none !important",
443
+ "--rowBorderColor": "none !important",
444
+ },
445
+ ...dataGridProps?.sx,
446
+ }}
447
+ />
448
+ </Box>
449
+ )
450
+ },
451
+ ),
452
+ )
453
+
454
+ WandelbotsDataGrid.displayName = "WandelbotsDataGrid"
@@ -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
+ )