@elizaos/client 1.6.1-alpha.3 → 1.6.1-alpha.5

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 (48) hide show
  1. package/dist/assets/{main-C4q5_rtN.js → main-4tyUgNqd.js} +3 -3
  2. package/dist/assets/{main-C4q5_rtN.js.map → main-4tyUgNqd.js.map} +1 -1
  3. package/dist/assets/{main-BNtEiK3o.js → main-Bbs84AcL.js} +77 -63
  4. package/dist/assets/main-Bbs84AcL.js.map +1 -0
  5. package/dist/assets/{main-BOBWcKWW.css → main-CNv6B3RZ.css} +597 -71
  6. package/dist/assets/react-vendor-DxnAFk-d.js +611 -0
  7. package/dist/assets/react-vendor-DxnAFk-d.js.map +1 -0
  8. package/dist/index.html +1 -1
  9. package/package.json +8 -4
  10. package/src/components/agent-prism/Avatar.tsx +164 -0
  11. package/src/components/agent-prism/Badge.tsx +109 -0
  12. package/src/components/agent-prism/Button.tsx +138 -0
  13. package/src/components/agent-prism/CollapseAndExpandControls.tsx +45 -0
  14. package/src/components/agent-prism/CollapsibleSection.tsx +121 -0
  15. package/src/components/agent-prism/DetailsView/DetailsView.tsx +141 -0
  16. package/src/components/agent-prism/DetailsView/DetailsViewAttributesTab.tsx +45 -0
  17. package/src/components/agent-prism/DetailsView/DetailsViewHeader.tsx +77 -0
  18. package/src/components/agent-prism/DetailsView/DetailsViewHeaderActions.tsx +21 -0
  19. package/src/components/agent-prism/DetailsView/DetailsViewInputOutputTab.tsx +210 -0
  20. package/src/components/agent-prism/DetailsView/DetailsViewMetrics.tsx +53 -0
  21. package/src/components/agent-prism/DetailsView/DetailsViewRawDataTab.tsx +24 -0
  22. package/src/components/agent-prism/IconButton.tsx +75 -0
  23. package/src/components/agent-prism/PriceBadge.tsx +12 -0
  24. package/src/components/agent-prism/SearchInput.tsx +17 -0
  25. package/src/components/agent-prism/SpanCard/SpanCard.tsx +467 -0
  26. package/src/components/agent-prism/SpanCard/SpanCardBadges.tsx +35 -0
  27. package/src/components/agent-prism/SpanCard/SpanCardConnector.tsx +36 -0
  28. package/src/components/agent-prism/SpanCard/SpanCardTimeline.tsx +60 -0
  29. package/src/components/agent-prism/SpanCard/SpanCardToggle.tsx +32 -0
  30. package/src/components/agent-prism/SpanStatus.tsx +79 -0
  31. package/src/components/agent-prism/Tabs.tsx +141 -0
  32. package/src/components/agent-prism/TextInput.tsx +142 -0
  33. package/src/components/agent-prism/TimestampBadge.tsx +28 -0
  34. package/src/components/agent-prism/TokensBadge.tsx +26 -0
  35. package/src/components/agent-prism/TraceList/TraceList.tsx +80 -0
  36. package/src/components/agent-prism/TraceList/TraceListItem.tsx +79 -0
  37. package/src/components/agent-prism/TraceList/TraceListItemHeader.tsx +46 -0
  38. package/src/components/agent-prism/TraceViewer.tsx +476 -0
  39. package/src/components/agent-prism/TreeView.tsx +57 -0
  40. package/src/components/agent-prism/shared.ts +210 -0
  41. package/src/components/agent-runs/AgentRunTimeline.tsx +64 -673
  42. package/src/components/agent-sidebar.tsx +2 -2
  43. package/src/components/chat.tsx +8 -8
  44. package/src/lib/agent-prism-utils.ts +46 -0
  45. package/src/lib/eliza-span-adapter.ts +487 -0
  46. package/dist/assets/main-BNtEiK3o.js.map +0 -1
  47. package/dist/assets/react-vendor-pe76PXQl.js +0 -546
  48. package/dist/assets/react-vendor-pe76PXQl.js.map +0 -1
@@ -0,0 +1,141 @@
1
+ import type { TraceSpan } from "@evilmartians/agent-prism-types";
2
+
3
+ import { cn } from "@/lib/utils";
4
+ import { SquareTerminal, Tags, ArrowRightLeft } from "lucide-react";
5
+ import { useState, type ReactElement, type ReactNode } from "react";
6
+
7
+ import type { AvatarProps } from "../Avatar";
8
+
9
+ import { Tabs, type TabItem } from "../Tabs";
10
+ import { DetailsViewAttributesTab } from "./DetailsViewAttributesTab";
11
+ import { DetailsViewHeader } from "./DetailsViewHeader";
12
+ import { DetailsViewInputOutputTab } from "./DetailsViewInputOutputTab";
13
+ import { DetailsViewMetrics } from "./DetailsViewMetrics";
14
+ import { DetailsViewRawDataTab } from "./DetailsViewRawDataTab";
15
+
16
+ type DetailsViewTab = "input-output" | "attributes" | "raw";
17
+
18
+ export interface DetailsViewProps {
19
+ /**
20
+ * The span data to display in the details view
21
+ */
22
+ data: TraceSpan;
23
+
24
+ /**
25
+ * Optional avatar configuration for the header
26
+ */
27
+ avatar?: AvatarProps;
28
+
29
+ /**
30
+ * The initially selected tab
31
+ */
32
+ defaultTab?: DetailsViewTab;
33
+
34
+ /**
35
+ * Optional className for the root container
36
+ */
37
+ className?: string;
38
+
39
+ /**
40
+ * Configuration for the copy button functionality
41
+ */
42
+ copyButton?: {
43
+ isEnabled?: boolean;
44
+ onCopy?: (data: TraceSpan) => void;
45
+ };
46
+
47
+ /**
48
+ * Custom header actions to render
49
+ * Can be a ReactNode or a render function that receives the data
50
+ */
51
+ headerActions?: ReactNode | ((data: TraceSpan) => ReactNode);
52
+
53
+ /**
54
+ * Optional custom header component to replace the default
55
+ */
56
+ customHeader?: ReactNode | ((props: { data: TraceSpan }) => ReactNode);
57
+
58
+ /**
59
+ * Callback fired when the active tab changes
60
+ */
61
+ onTabChange?: (tabValue: DetailsViewTab) => void;
62
+ }
63
+
64
+ export const DetailsView = ({
65
+ data,
66
+ avatar,
67
+ defaultTab,
68
+ className,
69
+ copyButton,
70
+ headerActions,
71
+ customHeader,
72
+ onTabChange,
73
+ }: DetailsViewProps): ReactElement => {
74
+ const [tab, setTab] = useState<DetailsViewTab>(defaultTab || "input-output");
75
+
76
+ const tabItems: TabItem<DetailsViewTab>[] = [
77
+ {
78
+ value: "input-output",
79
+ label: "In/Out",
80
+ icon: <ArrowRightLeft className="size-4" />,
81
+ },
82
+ {
83
+ value: "attributes",
84
+ label: "Attributes",
85
+ icon: <Tags className="size-4" />,
86
+ },
87
+ {
88
+ value: "raw",
89
+ label: "RAW",
90
+ icon: <SquareTerminal className="size-4" />,
91
+ },
92
+ ];
93
+
94
+ function handleTabChange(tabValue: DetailsViewTab) {
95
+ setTab(tabValue);
96
+ onTabChange?.(tabValue);
97
+ }
98
+
99
+ const resolvedHeaderActions =
100
+ typeof headerActions === "function" ? headerActions(data) : headerActions;
101
+
102
+ const headerContent = customHeader ? (
103
+ typeof customHeader === "function" ? (
104
+ customHeader({ data })
105
+ ) : (
106
+ customHeader
107
+ )
108
+ ) : (
109
+ <DetailsViewHeader
110
+ data={data}
111
+ avatar={avatar}
112
+ copyButton={copyButton}
113
+ actions={resolvedHeaderActions}
114
+ />
115
+ );
116
+
117
+ return (
118
+ <div
119
+ className={cn(
120
+ "min-w-0 rounded-lg border border-border bg-card p-4 shadow-sm",
121
+ className,
122
+ )}
123
+ >
124
+ {headerContent}
125
+
126
+ <DetailsViewMetrics data={data} />
127
+
128
+ <Tabs
129
+ items={tabItems}
130
+ value={tab}
131
+ onValueChange={handleTabChange}
132
+ theme="underline"
133
+ defaultValue={defaultTab}
134
+ />
135
+
136
+ {tab === "input-output" && <DetailsViewInputOutputTab data={data} />}
137
+ {tab === "attributes" && <DetailsViewAttributesTab data={data} />}
138
+ {tab === "raw" && <DetailsViewRawDataTab data={data} />}
139
+ </div>
140
+ );
141
+ };
@@ -0,0 +1,45 @@
1
+ import type { TraceSpan } from "@evilmartians/agent-prism-types";
2
+
3
+ import { CollapsibleSection } from "../CollapsibleSection";
4
+ import { TextInput } from "../TextInput";
5
+
6
+ interface AttributesTabProps {
7
+ data: TraceSpan;
8
+ }
9
+
10
+ export const DetailsViewAttributesTab = ({ data }: AttributesTabProps) => (
11
+ <div className="space-y-6">
12
+ {(!data.attributes || data.attributes.length === 0) && (
13
+ <div className="p-6 text-center">
14
+ <p className="text-muted-foreground ">
15
+ No attributes available for this span.
16
+ </p>
17
+ </div>
18
+ )}
19
+
20
+ {(data.attributes || []).map((attribute, index) => {
21
+ const value =
22
+ attribute.value.stringValue ||
23
+ attribute.value.intValue ||
24
+ attribute.value.boolValue?.toString() ||
25
+ "N/A";
26
+
27
+ return (
28
+ <CollapsibleSection
29
+ key={`${attribute.key}-${index}`}
30
+ title={attribute.key}
31
+ defaultOpen
32
+ >
33
+ <TextInput
34
+ id={`${data.id}-attribute-${index}`}
35
+ value={value}
36
+ disabled
37
+ readOnly
38
+ inputClassName="font-mono text-xs"
39
+ className="w-full"
40
+ />
41
+ </CollapsibleSection>
42
+ );
43
+ })}
44
+ </div>
45
+ );
@@ -0,0 +1,77 @@
1
+ import type { TraceSpan } from "@evilmartians/agent-prism-types";
2
+
3
+ import { Check, Copy } from "lucide-react";
4
+ import { useState, type ReactNode } from "react";
5
+
6
+ import { Avatar, type AvatarProps } from "../Avatar";
7
+ import { IconButton } from "../IconButton";
8
+ import { SpanStatus } from "../SpanStatus.tsx";
9
+
10
+ export interface DetailsViewHeaderProps {
11
+ data: TraceSpan;
12
+ avatar?: AvatarProps;
13
+ copyButton?: {
14
+ isEnabled?: boolean;
15
+ onCopy?: (data: TraceSpan) => void;
16
+ };
17
+ /**
18
+ * Custom actions to render in the header
19
+ */
20
+ actions?: ReactNode;
21
+ /**
22
+ * Optional className for the header container
23
+ */
24
+ className?: string;
25
+ }
26
+
27
+ export const DetailsViewHeader = ({
28
+ data,
29
+ avatar,
30
+ copyButton,
31
+ actions,
32
+ className = "mb-4 flex flex-wrap items-center gap-4",
33
+ }: DetailsViewHeaderProps) => {
34
+ const [hasCopied, setHasCopied] = useState(false);
35
+
36
+ const handleCopy = () => {
37
+ if (copyButton?.onCopy) {
38
+ copyButton.onCopy(data);
39
+ setHasCopied(true);
40
+ setTimeout(() => setHasCopied(false), 2000);
41
+ }
42
+ };
43
+
44
+ return (
45
+ <div className={className}>
46
+ <div className="flex items-center gap-1.5">
47
+ {avatar && <Avatar size="4" {...avatar} />}
48
+
49
+ <span className="text-base tracking-wide text-foreground ">
50
+ {data.title}
51
+ </span>
52
+
53
+ <div className="flex size-5 items-center justify-center">
54
+ <SpanStatus status={data.status} />
55
+ </div>
56
+
57
+ {copyButton && (
58
+ <IconButton
59
+ aria-label={
60
+ copyButton.isEnabled ? "Copy span details" : "Copy disabled"
61
+ }
62
+ variant="ghost"
63
+ onClick={handleCopy}
64
+ >
65
+ {hasCopied ? (
66
+ <Check className="size-3 text-muted-foreground" />
67
+ ) : (
68
+ <Copy className="size-3 text-muted-foreground" />
69
+ )}
70
+ </IconButton>
71
+ )}
72
+ </div>
73
+
74
+ {actions}
75
+ </div>
76
+ );
77
+ };
@@ -0,0 +1,21 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ export interface DetailsViewHeaderActionsProps {
4
+ /**
5
+ * Custom actions to render in the header
6
+ */
7
+ children?: ReactNode;
8
+ /**
9
+ * Optional className for the actions container
10
+ */
11
+ className?: string;
12
+ }
13
+
14
+ export const DetailsViewHeaderActions = ({
15
+ children,
16
+ className = "flex flex-wrap items-center gap-2",
17
+ }: DetailsViewHeaderActionsProps) => {
18
+ if (!children) return null;
19
+
20
+ return <div className={className}>{children}</div>;
21
+ };
@@ -0,0 +1,210 @@
1
+ import type { TraceSpan } from "@evilmartians/agent-prism-types";
2
+
3
+ import { Check, Copy } from "lucide-react";
4
+ import { useState, type ReactElement } from "react";
5
+ import JSONPretty from "react-json-pretty";
6
+ import colors from "tailwindcss/colors";
7
+
8
+ import { CollapsibleSection } from "../CollapsibleSection";
9
+ import { IconButton } from "../IconButton";
10
+ import { Tabs, type TabItem } from "../Tabs";
11
+
12
+ interface DetailsViewInputOutputTabProps {
13
+ data: TraceSpan;
14
+ }
15
+
16
+ type IOTab = "json" | "plain";
17
+
18
+ type IOSection = "Input" | "Output";
19
+
20
+ export const DetailsViewInputOutputTab = ({
21
+ data,
22
+ }: DetailsViewInputOutputTabProps): ReactElement => {
23
+ const hasInput = Boolean(data.input);
24
+ const hasOutput = Boolean(data.output);
25
+
26
+ if (!hasInput && !hasOutput) {
27
+ return (
28
+ <div className="p-6 text-center">
29
+ <p className="text-muted-foreground ">
30
+ No input or output data available for this span.
31
+ </p>
32
+ </div>
33
+ );
34
+ }
35
+
36
+ let parsedInput: string | null = null;
37
+ let parsedOutput: string | null = null;
38
+
39
+ if (typeof data.input === "string") {
40
+ try {
41
+ parsedInput = JSON.parse(data.input);
42
+ } catch {
43
+ parsedInput = null;
44
+ }
45
+ }
46
+
47
+ if (typeof data.output === "string") {
48
+ try {
49
+ parsedOutput = JSON.parse(data.output);
50
+ } catch {
51
+ parsedOutput = null;
52
+ }
53
+ }
54
+
55
+ return (
56
+ <div className="space-y-3">
57
+ {typeof data.input === "string" && (
58
+ <IOSection
59
+ section="Input"
60
+ content={data.input}
61
+ parsedContent={parsedInput}
62
+ />
63
+ )}
64
+
65
+ {typeof data.output === "string" && (
66
+ <IOSection
67
+ section="Output"
68
+ content={data.output}
69
+ parsedContent={parsedOutput}
70
+ />
71
+ )}
72
+ </div>
73
+ );
74
+ };
75
+
76
+ interface IOSectionProps {
77
+ section: IOSection;
78
+ content: string;
79
+ parsedContent: string | null;
80
+ }
81
+
82
+ const IOSection = ({
83
+ section,
84
+ content,
85
+ parsedContent,
86
+ }: IOSectionProps): ReactElement => {
87
+ const [tab, setTab] = useState<IOTab>("plain");
88
+ const [open, setOpen] = useState(true);
89
+
90
+ const tabItems: TabItem<IOTab>[] = [
91
+ {
92
+ value: "json",
93
+ label: "JSON",
94
+ disabled: !parsedContent,
95
+ },
96
+ {
97
+ value: "plain",
98
+ label: "Plain",
99
+ },
100
+ ];
101
+
102
+ return (
103
+ <CollapsibleSection
104
+ title={section}
105
+ defaultOpen
106
+ onOpenChange={setOpen}
107
+ rightContent={
108
+ open ? (
109
+ <Tabs<IOTab>
110
+ items={tabItems}
111
+ defaultValue="plain"
112
+ value={tab}
113
+ onValueChange={setTab}
114
+ theme="pill"
115
+ onClick={(event) => event.stopPropagation()}
116
+ />
117
+ ) : null
118
+ }
119
+ triggerClassName="min-h-16"
120
+ >
121
+ <IOContent
122
+ content={content}
123
+ section={section}
124
+ tab={tab}
125
+ parsedContent={parsedContent}
126
+ />
127
+ </CollapsibleSection>
128
+ );
129
+ };
130
+
131
+ interface IOContentProps extends Omit<IOSectionProps, "title"> {
132
+ tab: IOTab;
133
+ parsedContent: string | null;
134
+ }
135
+
136
+ const IOContent = ({
137
+ tab,
138
+ content,
139
+ section,
140
+ parsedContent,
141
+ }: IOContentProps): ReactElement => {
142
+ if (!content) {
143
+ return (
144
+ <p className="p-3 text-sm italic text-muted-foreground ">
145
+ No data available
146
+ </p>
147
+ );
148
+ }
149
+
150
+ return (
151
+ <div className="relative rounded-lg border border-border ">
152
+ <CopyButton section={section} content={content} />
153
+
154
+ {tab === "json" && (
155
+ <>
156
+ {parsedContent ? (
157
+ <JSONPretty
158
+ booleanStyle="color: hsl(var(--primary));"
159
+ className="overflow-x-auto rounded-xl p-4 text-left"
160
+ data={parsedContent}
161
+ id={`json-pretty-${section}`}
162
+ keyStyle="color: hsl(var(--primary));"
163
+ mainStyle="color: hsl(var(--muted-foreground)); font-size: 12px;"
164
+ stringStyle="color: hsl(var(--chart-2));"
165
+ valueStyle="color: hsl(var(--chart-1));"
166
+ />
167
+ ) : (
168
+ <div className="p-4 text-sm text-muted-foreground">
169
+ Invalid JSON format
170
+ </div>
171
+ )}
172
+ </>
173
+ )}
174
+
175
+ {tab === "plain" && (
176
+ <div className="rounded-lg bg-muted/50 p-4">
177
+ <pre className="overflow-x-auto whitespace-pre-wrap text-left font-mono text-xs text-foreground">
178
+ {content}
179
+ </pre>
180
+ </div>
181
+ )}
182
+ </div>
183
+ );
184
+ };
185
+
186
+ type CopyButtonProps = {
187
+ section: IOSection;
188
+ content: string;
189
+ };
190
+
191
+ const CopyButton = ({ section, content }: CopyButtonProps) => {
192
+ const [isCopied, setIsCopied] = useState(false);
193
+
194
+ const onClick = () => {
195
+ navigator.clipboard.writeText(content);
196
+ setIsCopied(true);
197
+ setTimeout(() => setIsCopied(false), 2000);
198
+ };
199
+
200
+ return (
201
+ <IconButton
202
+ onClick={onClick}
203
+ aria-label={isCopied ? `${section} Data Copied` : `Copy ${section} Data`}
204
+ variant="ghost"
205
+ className="absolute right-1.5 top-1.5"
206
+ >
207
+ {isCopied ? <Check className="size-3" /> : <Copy className="size-3" />}
208
+ </IconButton>
209
+ );
210
+ };
@@ -0,0 +1,53 @@
1
+ import type { TraceSpan } from "@evilmartians/agent-prism-types";
2
+
3
+ import { getDurationMs, formatDuration } from "@evilmartians/agent-prism-data";
4
+ import { Coins } from "lucide-react";
5
+
6
+ import { Badge } from "../Badge";
7
+ import {
8
+ getSpanCategoryIcon,
9
+ getSpanCategoryLabel,
10
+ getSpanCategoryTheme,
11
+ } from "../shared.ts";
12
+ import { TimestampBadge } from "../TimestampBadge.tsx";
13
+
14
+ interface DetailsViewMetricsProps {
15
+ data: TraceSpan;
16
+ }
17
+
18
+ export const DetailsViewMetrics = ({ data }: DetailsViewMetricsProps) => {
19
+ const Icon = getSpanCategoryIcon(data.type);
20
+ const durationMs = getDurationMs(data);
21
+
22
+ return (
23
+ <div className="mb-4 flex flex-wrap items-center justify-start gap-1">
24
+ <Badge
25
+ iconStart={<Icon className="size-2.5" />}
26
+ theme={getSpanCategoryTheme(data.type)}
27
+ size="4"
28
+ label={getSpanCategoryLabel(data.type)}
29
+ />
30
+
31
+ {typeof data.tokensCount === "number" && (
32
+ <Badge
33
+ iconStart={<Coins className="size-2.5" />}
34
+ theme="gray"
35
+ size="4"
36
+ label={data.tokensCount}
37
+ />
38
+ )}
39
+
40
+ {typeof data.cost === "number" && (
41
+ <Badge theme="gray" size="4" label={`$ ${data.cost.toFixed(4)}`} />
42
+ )}
43
+
44
+ <span className="text-xs text-muted-foreground">
45
+ LATENCY: {formatDuration(durationMs)}
46
+ </span>
47
+
48
+ {typeof data.startTime === "number" && (
49
+ <TimestampBadge timestamp={data.startTime} />
50
+ )}
51
+ </div>
52
+ );
53
+ };
@@ -0,0 +1,24 @@
1
+ import type { TraceSpan } from "@evilmartians/agent-prism-types";
2
+
3
+ import JSONPretty from "react-json-pretty";
4
+
5
+ interface RawDataTabProps {
6
+ data: TraceSpan;
7
+ }
8
+
9
+ export const DetailsViewRawDataTab = ({ data }: RawDataTabProps) => (
10
+ <div className="pt-4">
11
+ <div className="rounded border border-border bg-transparent ">
12
+ <JSONPretty
13
+ booleanStyle="color: hsl(var(--primary));"
14
+ className="overflow-x-auto rounded-xl p-4 text-left"
15
+ data={data.raw}
16
+ id={`json-pretty-${data.id || "span-details"}`}
17
+ keyStyle="color: hsl(var(--primary));"
18
+ mainStyle="color: hsl(var(--muted-foreground)); font-size: 12px;"
19
+ stringStyle="color: hsl(var(--chart-2));"
20
+ valueStyle="color: hsl(var(--chart-1));"
21
+ />
22
+ </div>
23
+ </div>
24
+ );
@@ -0,0 +1,75 @@
1
+ import type { ComponentPropsWithRef } from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ import type { ComponentSize } from "./shared";
6
+
7
+ type IconButtonSize = Extract<
8
+ ComponentSize,
9
+ "6" | "7" | "8" | "9" | "10" | "11" | "12" | "16"
10
+ >;
11
+ type IconButtonVariant = "default" | "ghost";
12
+
13
+ export type IconButtonProps = ComponentPropsWithRef<"button"> & {
14
+ /**
15
+ * The size of the icon button
16
+ */
17
+ size?: IconButtonSize;
18
+
19
+ /**
20
+ * The visual variant of the icon button
21
+ */
22
+ variant?: IconButtonVariant;
23
+
24
+ /**
25
+ * Accessible label for screen readers
26
+ * Required for accessibility compliance
27
+ */
28
+ "aria-label": string;
29
+ };
30
+
31
+ const sizeClasses: Record<IconButtonSize, string> = {
32
+ "6": "h-6 min-h-6",
33
+ "7": "h-7 min-h-7",
34
+ "8": "h-8 min-h-8",
35
+ "9": "h-9 min-h-9",
36
+ "10": "h-10 min-h-10",
37
+ "11": "h-11 min-h-11",
38
+ "12": "h-12 min-h-12",
39
+ "16": "h-16 min-h-16",
40
+ };
41
+
42
+ const variantClasses: Record<IconButtonVariant, string> = {
43
+ default: "border border-border bg-transparent ",
44
+ ghost: "bg-transparent",
45
+ };
46
+
47
+ // TODO: Remake to call Icon component directly instead of passing children
48
+ export const IconButton = ({
49
+ children,
50
+ className,
51
+ size = "6",
52
+ variant = "default",
53
+ type = "button",
54
+ "aria-label": ariaLabel,
55
+ ...rest
56
+ }: IconButtonProps) => {
57
+ return (
58
+ <button
59
+ type={type}
60
+ aria-label={ariaLabel}
61
+ className={cn(
62
+ className,
63
+ sizeClasses[size],
64
+ "inline-flex aspect-square shrink-0 items-center justify-center",
65
+ "rounded-md",
66
+ variantClasses[variant],
67
+ "text-muted-foreground ",
68
+ "hover:bg-gray-200 dark:hover:bg-gray-800",
69
+ )}
70
+ {...rest}
71
+ >
72
+ {children}
73
+ </button>
74
+ );
75
+ };
@@ -0,0 +1,12 @@
1
+ import type { ComponentPropsWithRef } from "react";
2
+
3
+ import { Badge, type BadgeProps } from "./Badge";
4
+
5
+ export type PriceBadgeProps = ComponentPropsWithRef<"span"> & {
6
+ cost: number;
7
+ size?: BadgeProps["size"];
8
+ };
9
+
10
+ export const PriceBadge = ({ cost, size, ...rest }: PriceBadgeProps) => {
11
+ return <Badge theme="gray" size={size} {...rest} label={`$ ${cost}`} />;
12
+ };
@@ -0,0 +1,17 @@
1
+ import { Search } from "lucide-react";
2
+
3
+ import { TextInput, type TextInputProps } from "./TextInput";
4
+
5
+ /**
6
+ * A simple wrapper around the TextInput component.
7
+ * It adds a search icon and a placeholder.
8
+ */
9
+ export const SearchInput = ({ ...props }: TextInputProps) => {
10
+ return (
11
+ <TextInput
12
+ startIcon={<Search className="size-4" />}
13
+ placeholder="Filter..."
14
+ {...props}
15
+ />
16
+ );
17
+ };