@workflow/web-shared 5.0.0-beta.0 → 5.0.0-beta.10
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 +1 -1
- package/dist/components/error-boundary.js +1 -1
- package/dist/components/event-list-view.d.ts +18 -1
- package/dist/components/event-list-view.d.ts.map +1 -1
- package/dist/components/event-list-view.js +242 -142
- package/dist/components/hook-actions.js +1 -1
- package/dist/components/index.d.ts +4 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +5 -2
- package/dist/components/new-trace-viewer/components/copy-button.d.ts +9 -0
- package/dist/components/new-trace-viewer/components/copy-button.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/copy-button.js +27 -0
- package/dist/components/new-trace-viewer/components/event-list.d.ts +10 -0
- package/dist/components/new-trace-viewer/components/event-list.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/event-list.js +39 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts +18 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js +54 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts +20 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js +81 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts +25 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js +101 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts +20 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js +114 -0
- package/dist/components/new-trace-viewer/components/split-pane.d.ts +14 -0
- package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/split-pane.js +99 -0
- package/dist/components/new-trace-viewer/components/timeline.d.ts +34 -0
- package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/timeline.js +223 -0
- package/dist/components/new-trace-viewer/context.d.ts +15 -0
- package/dist/components/new-trace-viewer/context.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/context.js +47 -0
- package/dist/components/new-trace-viewer/detail-panel.d.ts +10 -0
- package/dist/components/new-trace-viewer/detail-panel.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/detail-panel.js +13 -0
- package/dist/components/new-trace-viewer/icons.d.ts +6 -0
- package/dist/components/new-trace-viewer/icons.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/icons.js +15 -0
- package/dist/components/new-trace-viewer/search.d.ts +19 -0
- package/dist/components/new-trace-viewer/search.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/search.js +134 -0
- package/dist/components/new-trace-viewer/trace-viewer.d.ts +8 -0
- package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/trace-viewer.js +387 -0
- package/dist/components/new-trace-viewer/types.d.ts +1 -0
- package/dist/components/new-trace-viewer/types.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/types.js +2 -0
- package/dist/components/new-trace-viewer/util/constants.d.ts +1 -0
- package/dist/components/new-trace-viewer/util/constants.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/util/constants.js +2 -0
- package/dist/components/new-trace-viewer/utils.d.ts +46 -0
- package/dist/components/new-trace-viewer/utils.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/utils.js +330 -0
- package/dist/components/run-trace-view.d.ts +2 -1
- package/dist/components/run-trace-view.d.ts.map +1 -1
- package/dist/components/run-trace-view.js +3 -3
- package/dist/components/sidebar/attribute-panel.d.ts +7 -1
- package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
- package/dist/components/sidebar/attribute-panel.js +131 -70
- package/dist/components/sidebar/conversation-view.js +1 -1
- package/dist/components/sidebar/copyable-data-block.d.ts +1 -0
- package/dist/components/sidebar/copyable-data-block.d.ts.map +1 -1
- package/dist/components/sidebar/copyable-data-block.js +16 -15
- package/dist/components/sidebar/detail-card.d.ts +5 -6
- package/dist/components/sidebar/detail-card.d.ts.map +1 -1
- package/dist/components/sidebar/detail-card.js +32 -16
- package/dist/components/sidebar/entity-detail-panel.d.ts +3 -1
- package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
- package/dist/components/sidebar/entity-detail-panel.js +20 -37
- package/dist/components/sidebar/events-list.d.ts +1 -1
- package/dist/components/sidebar/events-list.d.ts.map +1 -1
- package/dist/components/sidebar/events-list.js +46 -32
- package/dist/components/sidebar/resolve-hook-modal.js +1 -1
- package/dist/components/sidebar/sidebar-data-context.d.ts +29 -0
- package/dist/components/sidebar/sidebar-data-context.d.ts.map +1 -0
- package/dist/components/sidebar/sidebar-data-context.js +19 -0
- package/dist/components/stream-viewer.d.ts +1 -1
- package/dist/components/stream-viewer.d.ts.map +1 -1
- package/dist/components/stream-viewer.js +3 -27
- package/dist/components/trace-viewer/components/map.js +1 -1
- package/dist/components/trace-viewer/components/markers.js +1 -1
- package/dist/components/trace-viewer/components/node.js +1 -1
- package/dist/components/trace-viewer/components/search-input.js +1 -1
- package/dist/components/trace-viewer/components/search.js +1 -1
- package/dist/components/trace-viewer/components/span-content.js +1 -1
- package/dist/components/trace-viewer/components/span-detail-panel.js +1 -1
- package/dist/components/trace-viewer/components/span-segments.js +1 -1
- package/dist/components/trace-viewer/components/span-strategies.js +1 -1
- package/dist/components/trace-viewer/components/ui.js +1 -1
- package/dist/components/trace-viewer/components/zoom-button.js +1 -1
- package/dist/components/trace-viewer/components/zoom-icons.js +1 -1
- package/dist/components/trace-viewer/context.js +1 -1
- package/dist/components/trace-viewer/index.js +1 -1
- package/dist/components/trace-viewer/trace-viewer.js +1 -1
- package/dist/components/trace-viewer/types.js +1 -1
- package/dist/components/trace-viewer/util/constants.js +1 -1
- package/dist/components/trace-viewer/util/scrollbar-width.js +1 -1
- package/dist/components/trace-viewer/util/timing.d.ts +1 -1
- package/dist/components/trace-viewer/util/timing.d.ts.map +1 -1
- package/dist/components/trace-viewer/util/timing.js +6 -9
- package/dist/components/trace-viewer/util/tree.js +1 -1
- package/dist/components/trace-viewer/util/use-immediate-style.js +1 -1
- package/dist/components/trace-viewer/util/use-streaming-spans.js +1 -1
- package/dist/components/trace-viewer/util/use-trackpad-zoom.js +1 -1
- package/dist/components/trace-viewer/worker.js +1 -1
- package/dist/components/trace-viewer-new.d.ts +10 -0
- package/dist/components/trace-viewer-new.d.ts.map +1 -0
- package/dist/components/trace-viewer-new.js +23 -0
- package/dist/components/ui/alert.js +1 -1
- package/dist/components/ui/button.d.ts +10 -0
- package/dist/components/ui/button.d.ts.map +1 -0
- package/dist/components/ui/button.js +46 -0
- package/dist/components/ui/card.js +1 -1
- package/dist/components/ui/data-inspector.d.ts +39 -1
- package/dist/components/ui/data-inspector.d.ts.map +1 -1
- package/dist/components/ui/data-inspector.js +207 -22
- package/dist/components/ui/decrypt-button.js +1 -1
- package/dist/components/ui/error-card.js +1 -1
- package/dist/components/ui/error-stack-block.d.ts.map +1 -1
- package/dist/components/ui/error-stack-block.js +27 -19
- package/dist/components/ui/icon-button.d.ts +13 -0
- package/dist/components/ui/icon-button.d.ts.map +1 -0
- package/dist/components/ui/icon-button.js +29 -0
- package/dist/components/ui/inspector-theme.d.ts +9 -10
- package/dist/components/ui/inspector-theme.d.ts.map +1 -1
- package/dist/components/ui/inspector-theme.js +39 -72
- package/dist/components/ui/load-more-button.js +1 -1
- package/dist/components/ui/menu-dropdown.js +1 -1
- package/dist/components/ui/skeleton.js +1 -1
- package/dist/components/ui/spinner.js +1 -1
- package/dist/components/ui/timestamp-tooltip.js +1 -1
- package/dist/components/workflow-trace-view.d.ts +3 -1
- package/dist/components/workflow-trace-view.d.ts.map +1 -1
- package/dist/components/workflow-trace-view.js +10 -5
- package/dist/components/workflow-traces/event-colors.js +1 -1
- package/dist/components/workflow-traces/trace-colors.js +1 -1
- package/dist/components/workflow-traces/trace-span-construction.d.ts +2 -2
- package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
- package/dist/components/workflow-traces/trace-span-construction.js +19 -6
- package/dist/components/workflow-traces/trace-time-utils.js +1 -1
- package/dist/hooks/use-dark-mode.js +1 -1
- package/dist/hooks/use-reduced-motion.d.ts +10 -0
- package/dist/hooks/use-reduced-motion.d.ts.map +1 -0
- package/dist/hooks/use-reduced-motion.js +29 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/lib/event-analysis.js +1 -1
- package/dist/lib/event-materialization.js +1 -1
- package/dist/lib/exact-event-search-id.d.ts +25 -0
- package/dist/lib/exact-event-search-id.d.ts.map +1 -0
- package/dist/lib/exact-event-search-id.js +39 -0
- package/dist/lib/hydration.d.ts +15 -2
- package/dist/lib/hydration.d.ts.map +1 -1
- package/dist/lib/hydration.js +168 -16
- package/dist/lib/stream-display.d.ts +18 -0
- package/dist/lib/stream-display.d.ts.map +1 -0
- package/dist/lib/stream-display.js +30 -0
- package/dist/lib/toast.js +1 -1
- package/dist/lib/trace-builder.d.ts.map +1 -1
- package/dist/lib/trace-builder.js +3 -2
- package/dist/lib/utils.d.ts +9 -9
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +27 -32
- package/dist/styles.css +475 -1
- package/package.json +6 -4
- package/src/components/event-list-view.tsx +447 -282
- package/src/components/index.ts +12 -1
- package/src/components/new-trace-viewer/components/copy-button.tsx +68 -0
- package/src/components/new-trace-viewer/components/event-list.tsx +127 -0
- package/src/components/new-trace-viewer/components/middle-truncate/copy-selection.ts +114 -0
- package/src/components/new-trace-viewer/components/middle-truncate/middle-truncate.tsx +157 -0
- package/src/components/new-trace-viewer/components/middle-truncate/truncate.ts +132 -0
- package/src/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.ts +167 -0
- package/src/components/new-trace-viewer/components/split-pane.tsx +185 -0
- package/src/components/new-trace-viewer/components/timeline.tsx +552 -0
- package/src/components/new-trace-viewer/context.tsx +92 -0
- package/src/components/new-trace-viewer/detail-panel.tsx +55 -0
- package/src/components/new-trace-viewer/icons.tsx +79 -0
- package/src/components/new-trace-viewer/search.ts +178 -0
- package/src/components/new-trace-viewer/trace-viewer.tsx +694 -0
- package/src/components/new-trace-viewer/types.ts +0 -0
- package/src/components/new-trace-viewer/util/constants.ts +0 -0
- package/src/components/new-trace-viewer/utils.ts +482 -0
- package/src/components/run-trace-view.tsx +3 -0
- package/src/components/sidebar/attribute-panel.tsx +240 -196
- package/src/components/sidebar/copyable-data-block.tsx +49 -27
- package/src/components/sidebar/detail-card.tsx +119 -59
- package/src/components/sidebar/entity-detail-panel.tsx +92 -148
- package/src/components/sidebar/events-list.tsx +93 -108
- package/src/components/sidebar/sidebar-data-context.tsx +62 -0
- package/src/components/stream-viewer.tsx +18 -40
- package/src/components/trace-viewer/util/timing.ts +5 -9
- package/src/components/trace-viewer-new.tsx +49 -0
- package/src/components/ui/button.tsx +68 -0
- package/src/components/ui/data-inspector.tsx +427 -31
- package/src/components/ui/error-stack-block.tsx +39 -30
- package/src/components/ui/icon-button.tsx +57 -0
- package/src/components/ui/inspector-theme.ts +39 -81
- package/src/components/workflow-trace-view.tsx +12 -2
- package/src/components/workflow-traces/trace-span-construction.ts +24 -6
- package/src/hooks/use-reduced-motion.ts +34 -0
- package/src/index.ts +9 -0
- package/src/lib/exact-event-search-id.ts +67 -0
- package/src/lib/hydration.ts +182 -14
- package/src/lib/stream-display.ts +48 -0
- package/src/lib/trace-builder.ts +2 -1
- package/src/lib/utils.ts +27 -32
- package/src/styles.css +475 -1
- package/dist/components/error-boundary.js.map +0 -1
- package/dist/components/event-list-view.js.map +0 -1
- package/dist/components/hook-actions.js.map +0 -1
- package/dist/components/index.js.map +0 -1
- package/dist/components/run-trace-view.js.map +0 -1
- package/dist/components/sidebar/attribute-panel.js.map +0 -1
- package/dist/components/sidebar/conversation-view.js.map +0 -1
- package/dist/components/sidebar/copyable-data-block.js.map +0 -1
- package/dist/components/sidebar/detail-card.js.map +0 -1
- package/dist/components/sidebar/entity-detail-panel.js.map +0 -1
- package/dist/components/sidebar/events-list.js.map +0 -1
- package/dist/components/sidebar/resolve-hook-modal.js.map +0 -1
- package/dist/components/stream-viewer.js.map +0 -1
- package/dist/components/trace-viewer/components/map.js.map +0 -1
- package/dist/components/trace-viewer/components/markers.js.map +0 -1
- package/dist/components/trace-viewer/components/node.js.map +0 -1
- package/dist/components/trace-viewer/components/search-input.js.map +0 -1
- package/dist/components/trace-viewer/components/search.js.map +0 -1
- package/dist/components/trace-viewer/components/span-content.js.map +0 -1
- package/dist/components/trace-viewer/components/span-detail-panel.js.map +0 -1
- package/dist/components/trace-viewer/components/span-segments.js.map +0 -1
- package/dist/components/trace-viewer/components/span-strategies.js.map +0 -1
- package/dist/components/trace-viewer/components/ui.js.map +0 -1
- package/dist/components/trace-viewer/components/zoom-button.js.map +0 -1
- package/dist/components/trace-viewer/components/zoom-icons.js.map +0 -1
- package/dist/components/trace-viewer/context.js.map +0 -1
- package/dist/components/trace-viewer/index.js.map +0 -1
- package/dist/components/trace-viewer/trace-viewer.js.map +0 -1
- package/dist/components/trace-viewer/types.js.map +0 -1
- package/dist/components/trace-viewer/util/constants.js.map +0 -1
- package/dist/components/trace-viewer/util/scrollbar-width.js.map +0 -1
- package/dist/components/trace-viewer/util/timing.js.map +0 -1
- package/dist/components/trace-viewer/util/tree.js.map +0 -1
- package/dist/components/trace-viewer/util/use-immediate-style.js.map +0 -1
- package/dist/components/trace-viewer/util/use-streaming-spans.js.map +0 -1
- package/dist/components/trace-viewer/util/use-trackpad-zoom.js.map +0 -1
- package/dist/components/trace-viewer/worker.js.map +0 -1
- package/dist/components/ui/alert.js.map +0 -1
- package/dist/components/ui/card.js.map +0 -1
- package/dist/components/ui/data-inspector.js.map +0 -1
- package/dist/components/ui/decrypt-button.js.map +0 -1
- package/dist/components/ui/error-card.js.map +0 -1
- package/dist/components/ui/error-stack-block.js.map +0 -1
- package/dist/components/ui/inspector-theme.js.map +0 -1
- package/dist/components/ui/load-more-button.js.map +0 -1
- package/dist/components/ui/menu-dropdown.js.map +0 -1
- package/dist/components/ui/skeleton.js.map +0 -1
- package/dist/components/ui/spinner.js.map +0 -1
- package/dist/components/ui/timestamp-tooltip.js.map +0 -1
- package/dist/components/workflow-trace-view.js.map +0 -1
- package/dist/components/workflow-traces/event-colors.js.map +0 -1
- package/dist/components/workflow-traces/trace-colors.js.map +0 -1
- package/dist/components/workflow-traces/trace-span-construction.js.map +0 -1
- package/dist/components/workflow-traces/trace-time-utils.js.map +0 -1
- package/dist/hooks/use-dark-mode.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/event-analysis.js.map +0 -1
- package/dist/lib/event-materialization.js.map +0 -1
- package/dist/lib/hydration.js.map +0 -1
- package/dist/lib/toast.js.map +0 -1
- package/dist/lib/trace-builder.js.map +0 -1
- package/dist/lib/utils.js.map +0 -1
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import type
|
|
4
|
-
import { useCallback,
|
|
5
|
-
import { isExpiredMarker } from '../../lib/hydration';
|
|
3
|
+
import { EVENT_DATA_REF_FIELDS, type Event } from '@workflow/world';
|
|
4
|
+
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
5
|
+
import { hasEncryptedFields, isExpiredMarker } from '../../lib/hydration';
|
|
6
6
|
import { ErrorCard } from '../ui/error-card';
|
|
7
7
|
import {
|
|
8
8
|
ErrorStackBlock,
|
|
9
9
|
isStructuredErrorWithStack,
|
|
10
10
|
} from '../ui/error-stack-block';
|
|
11
11
|
import { Skeleton } from '../ui/skeleton';
|
|
12
|
-
import {
|
|
13
|
-
import { CopyableDataBlock } from './copyable-data-block';
|
|
12
|
+
import { CopyableDataBlock, EncryptedDataBlock } from './copyable-data-block';
|
|
14
13
|
import { DetailCard } from './detail-card';
|
|
15
14
|
|
|
16
15
|
/**
|
|
@@ -55,62 +54,74 @@ function EventItem({
|
|
|
55
54
|
const [isLoading, setIsLoading] = useState(false);
|
|
56
55
|
const [loadError, setLoadError] = useState<string | null>(null);
|
|
57
56
|
const wasExpandedRef = useRef(false);
|
|
57
|
+
/** Mirrors whether we have a fetched payload; avoids stale `loadedData` in load closure. */
|
|
58
|
+
const loadedDataRef = useRef<unknown | null>(null);
|
|
58
59
|
|
|
59
60
|
// Check if the event already has eventData from the store
|
|
60
61
|
const existingData =
|
|
61
62
|
'eventData' in event && event.eventData != null ? event.eventData : null;
|
|
62
|
-
const
|
|
63
|
+
const mergedDisplay = loadedData ?? existingData;
|
|
63
64
|
const canHaveData = DATA_EVENT_TYPES.has(event.eventType);
|
|
64
65
|
|
|
65
|
-
const loadEventData = useCallback(
|
|
66
|
-
|
|
66
|
+
const loadEventData = useCallback(
|
|
67
|
+
async (options?: { force?: boolean }) => {
|
|
68
|
+
if (!onLoadEventData || !event.correlationId || !event.eventId) return;
|
|
69
|
+
if (!options?.force && loadedDataRef.current !== null) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
67
72
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
try {
|
|
74
|
+
setIsLoading(true);
|
|
75
|
+
setLoadError(null);
|
|
76
|
+
const data = await onLoadEventData(event.correlationId, event.eventId);
|
|
77
|
+
loadedDataRef.current = data;
|
|
78
|
+
setLoadedData(data);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
setLoadError(err instanceof Error ? err.message : String(err));
|
|
81
|
+
} finally {
|
|
82
|
+
setIsLoading(false);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
[onLoadEventData, event.correlationId, event.eventId]
|
|
86
|
+
);
|
|
79
87
|
|
|
80
88
|
const handleExpand = useCallback(async () => {
|
|
81
|
-
if (
|
|
89
|
+
if (isLoading) return;
|
|
82
90
|
wasExpandedRef.current = true;
|
|
83
91
|
await loadEventData();
|
|
84
|
-
}, [
|
|
92
|
+
}, [isLoading, loadEventData]);
|
|
85
93
|
|
|
86
94
|
// When the encryption key changes and this event was previously expanded,
|
|
87
95
|
// re-load the data so it gets decrypted
|
|
88
|
-
|
|
89
|
-
if (encryptionKey
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
}, [encryptionKey]);
|
|
96
|
+
useLayoutEffect(() => {
|
|
97
|
+
if (!encryptionKey || !wasExpandedRef.current) return;
|
|
98
|
+
loadedDataRef.current = null;
|
|
99
|
+
setLoadedData(null);
|
|
100
|
+
void loadEventData({ force: true });
|
|
101
|
+
}, [encryptionKey, loadEventData]);
|
|
95
102
|
|
|
96
103
|
const createdAt = new Date(event.createdAt);
|
|
104
|
+
const createdAtTime = createdAt.toLocaleTimeString(undefined, {
|
|
105
|
+
hour: 'numeric',
|
|
106
|
+
minute: 'numeric',
|
|
107
|
+
second: 'numeric',
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const displayPayload = isLoading ? loadedData : mergedDisplay;
|
|
97
111
|
|
|
98
112
|
return (
|
|
99
113
|
<DetailCard
|
|
100
|
-
|
|
114
|
+
variant="card"
|
|
115
|
+
summaryClassName="px-3 py-2"
|
|
101
116
|
summary={
|
|
102
|
-
|
|
103
|
-
<span
|
|
104
|
-
className="font-medium"
|
|
105
|
-
style={{ color: 'var(--ds-gray-1000)' }}
|
|
106
|
-
>
|
|
117
|
+
<div className="flex w-full items-center justify-between gap-3">
|
|
118
|
+
<span className="text-gray-1000 text-label-12 font-mono">
|
|
107
119
|
{event.eventType}
|
|
108
|
-
</span>{' '}
|
|
109
|
-
-{' '}
|
|
110
|
-
<span style={{ color: 'var(--ds-gray-700)' }}>
|
|
111
|
-
{localMillisecondTime(createdAt.getTime())}
|
|
112
120
|
</span>
|
|
113
|
-
|
|
121
|
+
<span className="shrink-0 text-label-13 text-gray-900">
|
|
122
|
+
{createdAtTime}
|
|
123
|
+
</span>
|
|
124
|
+
</div>
|
|
114
125
|
}
|
|
115
126
|
onToggle={
|
|
116
127
|
canHaveData
|
|
@@ -121,44 +132,17 @@ function EventItem({
|
|
|
121
132
|
}
|
|
122
133
|
>
|
|
123
134
|
{/* Event attributes */}
|
|
124
|
-
<div
|
|
125
|
-
className="flex
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
backgroundColor: 'var(--ds-gray-100)',
|
|
129
|
-
}}
|
|
130
|
-
>
|
|
131
|
-
<div
|
|
132
|
-
className="flex min-h-[32px] items-center justify-between gap-4 px-2.5 py-1.5"
|
|
133
|
-
style={{ borderColor: 'var(--ds-gray-300)' }}
|
|
134
|
-
>
|
|
135
|
-
<span className="text-[14px]" style={{ color: 'var(--ds-gray-700)' }}>
|
|
136
|
-
eventId
|
|
137
|
-
</span>
|
|
138
|
-
<span
|
|
139
|
-
className="max-w-[70%] truncate text-right text-[13px] font-mono"
|
|
140
|
-
style={{ color: 'var(--ds-gray-1000)' }}
|
|
141
|
-
title={event.eventId}
|
|
142
|
-
>
|
|
135
|
+
<div className="flex flex-col bg-background-200 [&:has(+_*)]:border-b [&:has(+_*)]:border-gray-alpha-400">
|
|
136
|
+
<div className="flex items-center justify-between gap-2 py-2 px-3">
|
|
137
|
+
<span className="text-label-12 text-gray-900">Event ID</span>
|
|
138
|
+
<span className="max-w-[70%] truncate text-right text-label-12 font-mono">
|
|
143
139
|
{event.eventId}
|
|
144
140
|
</span>
|
|
145
141
|
</div>
|
|
146
142
|
{event.correlationId && (
|
|
147
|
-
<div
|
|
148
|
-
className="
|
|
149
|
-
|
|
150
|
-
>
|
|
151
|
-
<span
|
|
152
|
-
className="text-[14px]"
|
|
153
|
-
style={{ color: 'var(--ds-gray-700)' }}
|
|
154
|
-
>
|
|
155
|
-
correlationId
|
|
156
|
-
</span>
|
|
157
|
-
<span
|
|
158
|
-
className="max-w-[70%] truncate text-right text-[13px] font-mono"
|
|
159
|
-
style={{ color: 'var(--ds-gray-1000)' }}
|
|
160
|
-
title={event.correlationId}
|
|
161
|
-
>
|
|
143
|
+
<div className="flex items-center justify-between gap-2 py-2 px-3">
|
|
144
|
+
<span className="text-label-12 text-gray-900">Correlation ID</span>
|
|
145
|
+
<span className="max-w-[70%] truncate text-right text-label-12 font-mono">
|
|
162
146
|
{event.correlationId}
|
|
163
147
|
</span>
|
|
164
148
|
</div>
|
|
@@ -167,12 +151,7 @@ function EventItem({
|
|
|
167
151
|
|
|
168
152
|
{/* Loading state */}
|
|
169
153
|
{isLoading && (
|
|
170
|
-
<div
|
|
171
|
-
className="mt-2 rounded-md border p-3"
|
|
172
|
-
style={{
|
|
173
|
-
borderColor: 'var(--ds-gray-300)',
|
|
174
|
-
}}
|
|
175
|
-
>
|
|
154
|
+
<div className="p-3">
|
|
176
155
|
<Skeleton className="h-4 w-[35%]" />
|
|
177
156
|
<Skeleton className="mt-2 h-4 w-[90%]" />
|
|
178
157
|
<Skeleton className="mt-2 h-4 w-[75%]" />
|
|
@@ -189,9 +168,9 @@ function EventItem({
|
|
|
189
168
|
)}
|
|
190
169
|
|
|
191
170
|
{/* Event data */}
|
|
192
|
-
{
|
|
193
|
-
<div className="
|
|
194
|
-
<EventDataBlock eventType={event.eventType} data={
|
|
171
|
+
{displayPayload != null && (
|
|
172
|
+
<div className="[&>div]:border-none [&>div]:rounded-none">
|
|
173
|
+
<EventDataBlock eventType={event.eventType} data={displayPayload} />
|
|
195
174
|
</div>
|
|
196
175
|
)}
|
|
197
176
|
</DetailCard>
|
|
@@ -199,17 +178,17 @@ function EventItem({
|
|
|
199
178
|
}
|
|
200
179
|
|
|
201
180
|
/**
|
|
202
|
-
* Check if an eventData object has only expired marker values in
|
|
203
|
-
*
|
|
204
|
-
*
|
|
181
|
+
* Check if an eventData object has only expired marker values in ref/payload
|
|
182
|
+
* fields for this event type (see {@link EVENT_DATA_REF_FIELDS}). Other keys
|
|
183
|
+
* (e.g. `resumeAt`, `stepName`) are ignored.
|
|
205
184
|
*/
|
|
206
|
-
function hasOnlyExpiredFields(data: unknown): boolean {
|
|
185
|
+
function hasOnlyExpiredFields(data: unknown, eventType: string): boolean {
|
|
207
186
|
if (data === null || typeof data !== 'object' || Array.isArray(data)) {
|
|
208
187
|
return false;
|
|
209
188
|
}
|
|
210
189
|
const record = data as Record<string, unknown>;
|
|
211
|
-
const
|
|
212
|
-
const presentKeys =
|
|
190
|
+
const refKeys = EVENT_DATA_REF_FIELDS[eventType] ?? [];
|
|
191
|
+
const presentKeys = refKeys.filter((k) => k in record);
|
|
213
192
|
return (
|
|
214
193
|
presentKeys.length > 0 &&
|
|
215
194
|
presentKeys.every((k) => isExpiredMarker(record[k]))
|
|
@@ -230,7 +209,7 @@ function EventDataBlock({
|
|
|
230
209
|
// Expired data — show a simple message instead of the raw stub.
|
|
231
210
|
// Check both the top-level eventData and nested sub-fields (result, input, etc.)
|
|
232
211
|
// since the server stubs each ref field independently.
|
|
233
|
-
if (isExpiredMarker(data) || hasOnlyExpiredFields(data)) {
|
|
212
|
+
if (isExpiredMarker(data) || hasOnlyExpiredFields(data, eventType)) {
|
|
234
213
|
return (
|
|
235
214
|
<div
|
|
236
215
|
className="flex items-center gap-1.5 rounded-md border px-3 py-2 text-xs"
|
|
@@ -245,6 +224,10 @@ function EventDataBlock({
|
|
|
245
224
|
);
|
|
246
225
|
}
|
|
247
226
|
|
|
227
|
+
if (hasEncryptedFields({ eventType, eventData: data })) {
|
|
228
|
+
return <EncryptedDataBlock />;
|
|
229
|
+
}
|
|
230
|
+
|
|
248
231
|
// For error events (step_failed, step_retrying), the eventData has the shape
|
|
249
232
|
// { error: StructuredError, stack?: string, ... }. Check both the top-level
|
|
250
233
|
// value and the nested `error` field for a stack trace.
|
|
@@ -298,26 +281,28 @@ export function EventsList({
|
|
|
298
281
|
[events]
|
|
299
282
|
);
|
|
300
283
|
|
|
284
|
+
const hasEvents = sortedEvents.length > 0 && !error;
|
|
285
|
+
|
|
286
|
+
if (!hasEvents && !isLoading) {
|
|
287
|
+
return <DetailCard summary="Events" disabled />;
|
|
288
|
+
}
|
|
289
|
+
|
|
301
290
|
return (
|
|
302
|
-
<
|
|
303
|
-
<h3
|
|
304
|
-
className="text-heading-16 font-medium mt-4 mb-2"
|
|
305
|
-
style={{ color: 'var(--ds-gray-1000)' }}
|
|
306
|
-
>
|
|
307
|
-
Events {!isLoading && `(${sortedEvents.length})`}
|
|
308
|
-
</h3>
|
|
291
|
+
<DetailCard summary="Events" contentClassName="mb-0" defaultOpen>
|
|
309
292
|
{isLoading ? (
|
|
310
|
-
<div className="flex flex-col
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
293
|
+
<div className="flex flex-col -mx-4">
|
|
294
|
+
{[0, 1, 2].map((i) => (
|
|
295
|
+
<div
|
|
296
|
+
key={i}
|
|
297
|
+
className="flex items-center justify-between gap-3 bg-background-200 px-4 py-2"
|
|
298
|
+
>
|
|
299
|
+
<Skeleton className="h-4 w-32 rounded" />
|
|
300
|
+
<Skeleton className="h-3 w-16 rounded" />
|
|
301
|
+
</div>
|
|
302
|
+
))}
|
|
314
303
|
</div>
|
|
315
|
-
) :
|
|
316
|
-
|
|
317
|
-
<div className="text-sm">No events found</div>
|
|
318
|
-
)}
|
|
319
|
-
{sortedEvents.length > 0 && !error ? (
|
|
320
|
-
<div className="flex flex-col gap-4">
|
|
304
|
+
) : (
|
|
305
|
+
<div className="flex flex-col -mx-4">
|
|
321
306
|
{sortedEvents.map((event) => (
|
|
322
307
|
<EventItem
|
|
323
308
|
key={event.eventId}
|
|
@@ -327,7 +312,7 @@ export function EventsList({
|
|
|
327
312
|
/>
|
|
328
313
|
))}
|
|
329
314
|
</div>
|
|
330
|
-
)
|
|
331
|
-
</
|
|
315
|
+
)}
|
|
316
|
+
</DetailCard>
|
|
332
317
|
);
|
|
333
318
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, type ReactNode } from 'react';
|
|
4
|
+
import type { Event, Hook, Step, WorkflowRun } from '@workflow/world';
|
|
5
|
+
import type { SpanSelectionInfo } from './entity-detail-panel';
|
|
6
|
+
|
|
7
|
+
export interface SidebarDataContextValue {
|
|
8
|
+
run: WorkflowRun;
|
|
9
|
+
events: Event[];
|
|
10
|
+
spanDetailData: WorkflowRun | Step | Hook | Event | null;
|
|
11
|
+
spanDetailError?: Error | null;
|
|
12
|
+
spanDetailLoading?: boolean;
|
|
13
|
+
onSpanSelect: (info: SpanSelectionInfo) => void;
|
|
14
|
+
onStreamClick?: (streamId: string) => void;
|
|
15
|
+
onRunClick?: (runId: string) => void;
|
|
16
|
+
onWakeUpSleep?: (
|
|
17
|
+
runId: string,
|
|
18
|
+
correlationId: string
|
|
19
|
+
) => Promise<{ stoppedCount: number }>;
|
|
20
|
+
onLoadEventData?: (
|
|
21
|
+
correlationId: string,
|
|
22
|
+
eventId: string
|
|
23
|
+
) => Promise<unknown | null>;
|
|
24
|
+
onResolveHook?: (
|
|
25
|
+
hookToken: string,
|
|
26
|
+
payload: unknown,
|
|
27
|
+
hook?: Hook
|
|
28
|
+
) => Promise<void>;
|
|
29
|
+
encryptionKey?: Uint8Array;
|
|
30
|
+
onDecrypt?: () => void;
|
|
31
|
+
isDecrypting?: boolean;
|
|
32
|
+
hasEncryptedData?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const SidebarDataContext = createContext<SidebarDataContextValue | null>(null);
|
|
36
|
+
SidebarDataContext.displayName = 'SidebarDataContext';
|
|
37
|
+
|
|
38
|
+
export function SidebarDataProvider({
|
|
39
|
+
value,
|
|
40
|
+
children,
|
|
41
|
+
}: {
|
|
42
|
+
value: SidebarDataContextValue;
|
|
43
|
+
children: ReactNode;
|
|
44
|
+
}) {
|
|
45
|
+
return (
|
|
46
|
+
<SidebarDataContext.Provider value={value}>
|
|
47
|
+
{children}
|
|
48
|
+
</SidebarDataContext.Provider>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function useSidebarData(): SidebarDataContextValue {
|
|
53
|
+
const ctx = useContext(SidebarDataContext);
|
|
54
|
+
if (!ctx) {
|
|
55
|
+
throw new Error('useSidebarData must be used within a SidebarDataProvider');
|
|
56
|
+
}
|
|
57
|
+
return ctx;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function useSidebarDataOptional(): SidebarDataContextValue | null {
|
|
61
|
+
return useContext(SidebarDataContext);
|
|
62
|
+
}
|
|
@@ -5,37 +5,13 @@ import { Virtuoso, type VirtuosoHandle } from 'react-virtuoso';
|
|
|
5
5
|
import { DataInspector } from './ui/data-inspector';
|
|
6
6
|
import { Skeleton } from './ui/skeleton';
|
|
7
7
|
|
|
8
|
-
// ──────────────────────────────────────────────────────────────────────────
|
|
9
|
-
// Helpers
|
|
10
|
-
// ──────────────────────────────────────────────────────────────────────────
|
|
11
|
-
|
|
12
|
-
function deserializeChunkText(text: string): string {
|
|
13
|
-
try {
|
|
14
|
-
const parsed = JSON.parse(text);
|
|
15
|
-
if (typeof parsed === 'string') {
|
|
16
|
-
return parsed;
|
|
17
|
-
}
|
|
18
|
-
return JSON.stringify(parsed, null, 2);
|
|
19
|
-
} catch {
|
|
20
|
-
return text;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function parseChunkData(text: string): unknown {
|
|
25
|
-
try {
|
|
26
|
-
return JSON.parse(text);
|
|
27
|
-
} catch {
|
|
28
|
-
return text;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
8
|
// ──────────────────────────────────────────────────────────────────────────
|
|
33
9
|
// Types
|
|
34
10
|
// ──────────────────────────────────────────────────────────────────────────
|
|
35
11
|
|
|
36
12
|
export interface StreamChunk {
|
|
37
13
|
id: number;
|
|
38
|
-
|
|
14
|
+
value: unknown;
|
|
39
15
|
}
|
|
40
16
|
|
|
41
17
|
type Chunk = StreamChunk;
|
|
@@ -62,8 +38,6 @@ const ChunkRow = React.memo(function ChunkRow({
|
|
|
62
38
|
chunk: Chunk;
|
|
63
39
|
index: number;
|
|
64
40
|
}) {
|
|
65
|
-
const parsed = parseChunkData(chunk.text);
|
|
66
|
-
|
|
67
41
|
return (
|
|
68
42
|
<div
|
|
69
43
|
className="text-[11px] rounded-md border p-3"
|
|
@@ -72,22 +46,26 @@ const ChunkRow = React.memo(function ChunkRow({
|
|
|
72
46
|
backgroundColor: 'var(--ds-gray-100)',
|
|
73
47
|
}}
|
|
74
48
|
>
|
|
75
|
-
<
|
|
76
|
-
className="select-none mr-2"
|
|
77
|
-
style={{ color: 'var(--ds-gray-500)' }}
|
|
78
|
-
>
|
|
79
|
-
[{index}]
|
|
80
|
-
</span>
|
|
81
|
-
{typeof parsed === 'string' ? (
|
|
49
|
+
<div className="flex items-start gap-2">
|
|
82
50
|
<span
|
|
83
|
-
className="
|
|
84
|
-
style={{ color: 'var(--ds-gray-
|
|
51
|
+
className="select-none pt-px"
|
|
52
|
+
style={{ color: 'var(--ds-gray-500)' }}
|
|
85
53
|
>
|
|
86
|
-
{
|
|
54
|
+
[{index}]
|
|
87
55
|
</span>
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
56
|
+
<div className="min-w-0 flex-1">
|
|
57
|
+
{typeof chunk.value === 'string' ? (
|
|
58
|
+
<span
|
|
59
|
+
className="whitespace-pre-wrap break-words"
|
|
60
|
+
style={{ color: 'var(--ds-gray-1000)' }}
|
|
61
|
+
>
|
|
62
|
+
{chunk.value}
|
|
63
|
+
</span>
|
|
64
|
+
) : (
|
|
65
|
+
<DataInspector data={chunk.value} expandLevel={1} />
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
91
69
|
</div>
|
|
92
70
|
);
|
|
93
71
|
});
|
|
@@ -14,22 +14,18 @@ export function getMsInHighRes(ms: number): [number, number] {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Formats a duration for timeline display (compact
|
|
17
|
+
* Formats a duration for timeline display (compact format).
|
|
18
18
|
* @deprecated Use formatDuration(ms, true) instead.
|
|
19
19
|
*/
|
|
20
20
|
export const formatDurationForTimeline = (ms: number): string =>
|
|
21
21
|
formatDuration(ms, true);
|
|
22
22
|
|
|
23
|
-
const timeSelectionFormatter = new Intl.NumberFormat(undefined, {
|
|
24
|
-
minimumFractionDigits: 2,
|
|
25
|
-
maximumFractionDigits: 2,
|
|
26
|
-
});
|
|
27
|
-
|
|
28
23
|
export function formatTimeSelection(ms: number): string {
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
const roundedMs = Math.round(ms);
|
|
25
|
+
if (roundedMs < 1000) {
|
|
26
|
+
return `${roundedMs}ms`;
|
|
31
27
|
}
|
|
32
|
-
return `${
|
|
28
|
+
return `${Math.round(ms / 1000)}s`;
|
|
33
29
|
}
|
|
34
30
|
|
|
35
31
|
/**
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import type { WorkflowRun } from '@workflow/core/runtime';
|
|
3
|
+
import type { Event } from '@workflow/world';
|
|
4
|
+
import { buildTrace, type TraceWithMeta } from '../lib/trace-builder';
|
|
5
|
+
import { NewTraceViewer as NewTraceViewerComponent } from './new-trace-viewer/trace-viewer';
|
|
6
|
+
import {
|
|
7
|
+
SidebarDataProvider,
|
|
8
|
+
type SidebarDataContextValue,
|
|
9
|
+
} from './sidebar/sidebar-data-context';
|
|
10
|
+
import type { Trace } from './trace-viewer/types';
|
|
11
|
+
|
|
12
|
+
const NewTraceViewer = ({
|
|
13
|
+
run,
|
|
14
|
+
events,
|
|
15
|
+
sidebarData,
|
|
16
|
+
}: {
|
|
17
|
+
run: WorkflowRun;
|
|
18
|
+
events: Event[];
|
|
19
|
+
sidebarData: SidebarDataContextValue;
|
|
20
|
+
}) => {
|
|
21
|
+
// Build trace only when actual data changes — no timer-driven rebuilds.
|
|
22
|
+
// Active span widths are animated imperatively by useLiveTick at 60fps.
|
|
23
|
+
const traceWithMeta: TraceWithMeta | undefined = useMemo(() => {
|
|
24
|
+
if (!run?.runId) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
return buildTrace(run, events, new Date());
|
|
28
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- `new Date()` is intentionally not a dep; useLiveTick handles live growth
|
|
29
|
+
}, [run, events]);
|
|
30
|
+
const trace = traceWithMeta;
|
|
31
|
+
|
|
32
|
+
if (!trace) {
|
|
33
|
+
return (
|
|
34
|
+
<div className="relative w-full h-full flex items-center justify-center">
|
|
35
|
+
<div className="text-gray-500 text-sm">Loading trace…</div>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<SidebarDataProvider value={sidebarData}>
|
|
42
|
+
<div className="relative w-full h-full flex">
|
|
43
|
+
<NewTraceViewerComponent trace={trace as Trace} />
|
|
44
|
+
</div>
|
|
45
|
+
</SidebarDataProvider>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export { NewTraceViewer };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
import { cn } from '../../lib/utils';
|
|
4
|
+
|
|
5
|
+
const buttonVariants = cva(
|
|
6
|
+
[
|
|
7
|
+
'outline-none m-0 border-0 align-baseline no-underline group/trigger',
|
|
8
|
+
'relative cursor-pointer select-none transform translate-z-0',
|
|
9
|
+
'inline-flex max-w-full items-center justify-center gap-x-1 whitespace-nowrap rounded-md font-medium',
|
|
10
|
+
'!text-gray-100',
|
|
11
|
+
'bg-gray-1000',
|
|
12
|
+
'transition-[border-color,background,color,transform,box-shadow] duration-150 ease-in-out',
|
|
13
|
+
// Geist invert hover: literal fallbacks (no token dep, so it resolves in any
|
|
14
|
+
// consuming app), darkened via ancestor theme class without a registered variant.
|
|
15
|
+
'hover:bg-[var(--themed-hover-bg,_hsl(0,_0%,_22%))]',
|
|
16
|
+
'[.dark-theme_&]:hover:bg-[var(--themed-hover-bg,_hsl(0,_0%,_80%))]',
|
|
17
|
+
'[[data-theme=dark]_&]:hover:bg-[var(--themed-hover-bg,_hsl(0,_0%,_80%))]',
|
|
18
|
+
// Geist focus ring as arbitrary properties (no bare `outline`/ambiguous
|
|
19
|
+
// arbitrary-color utilities, which differ between Tailwind v3 and v4).
|
|
20
|
+
'focus-visible:[outline:2px_solid_var(--ds-focus-color)] focus-visible:[outline-offset:2px]',
|
|
21
|
+
// disabled styles
|
|
22
|
+
'disabled:cursor-not-allowed aria-disabled:cursor-not-allowed',
|
|
23
|
+
'disabled:bg-gray-100 disabled:!text-gray-700 disabled:hover:bg-gray-100',
|
|
24
|
+
'aria-disabled:bg-gray-100 aria-disabled:text-gray-700 aria-disabled:hover:bg-gray-100',
|
|
25
|
+
],
|
|
26
|
+
{
|
|
27
|
+
variants: {
|
|
28
|
+
variant: {
|
|
29
|
+
default: '',
|
|
30
|
+
secondary:
|
|
31
|
+
'border border-gray-alpha-400 [--themed-bg:_var(--ds-background-100)] [--themed-fg:_var(--ds-gray-1000)] [--themed-hover-bg:_var(--ds-gray-alpha-200)]',
|
|
32
|
+
ghost:
|
|
33
|
+
'[--themed-bg:_transparent] [--themed-fg:_var(--ds-gray-1000)] [--themed-hover-bg:_var(--ds-gray-alpha-100)]',
|
|
34
|
+
},
|
|
35
|
+
size: {
|
|
36
|
+
default: 'h-10 px-4 text-[14px]',
|
|
37
|
+
sm: 'h-8 px-3 text-[14px]',
|
|
38
|
+
xs: 'h-6 px-1.5 py-0.5 text-button-12 rounded-[4px]',
|
|
39
|
+
icon: 'h-8 w-8',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
defaultVariants: {
|
|
43
|
+
variant: 'default',
|
|
44
|
+
size: 'default',
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
|
|
50
|
+
VariantProps<typeof buttonVariants>;
|
|
51
|
+
|
|
52
|
+
export function Button({
|
|
53
|
+
className,
|
|
54
|
+
variant,
|
|
55
|
+
size,
|
|
56
|
+
type = 'button',
|
|
57
|
+
...props
|
|
58
|
+
}: ButtonProps) {
|
|
59
|
+
return (
|
|
60
|
+
<button
|
|
61
|
+
type={type}
|
|
62
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { buttonVariants };
|