Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 38 additions & 16 deletions apps/start/src/components/ui/key-value-grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,35 @@ interface KeyValueGridProps {
copyable?: boolean;
}

/**
* The value cell truncates, so the full value is only reachable through the native
* tooltip and the copy button. Both need a string, and objects render as JSON here,
* so stringify them rather than leaving the value unreadable.
*/
export function toStringValue(value: unknown): string | undefined {
if (value === null || value === undefined) {
return undefined;
}

if (typeof value === 'string') {
return value;
}

if (value instanceof Date) {
return value.toISOString();
}

if (typeof value === 'object') {
try {
return JSON.stringify(value);
} catch {
return undefined;
}
}

return String(value);
}

export function KeyValueGrid({
data,
columns = 1,
Expand Down Expand Up @@ -80,7 +109,10 @@ export function KeyValueGrid({
<div
className={cn('grid card overflow-hidden', gridCols[columns], className)}
>
{data.map((item, index) => (
{data.map((item, index) => {
const stringValue = toStringValue(item.value);

return (
<div
key={`${item.name}-${index}`}
className={cn(
Expand All @@ -98,22 +130,11 @@ export function KeyValueGrid({
tabIndex={onItemClick ? 0 : undefined}
role={onItemClick ? 'button' : undefined}
>
{copyable && (
{copyable && stringValue !== undefined && (
<button
onClick={(e) => {
e.stopPropagation();
if (typeof item.value === 'object') {
try {
const value = JSON.stringify(item.value);
clipboard(value);
}
catch {
clipboard(item.value);
}
}
else {
clipboard(item.value);
}
clipboard(stringValue);
}}
type="button"
className="absolute left-2 top-1/2 -translate-y-1/2 -translate-x-full opacity-0 group-hover:translate-x-0 group-hover:opacity-100 transition-all duration-200 ease-out bg-background border border-border rounded p-1 shadow-sm z-10"
Expand All @@ -129,12 +150,13 @@ export function KeyValueGrid({
'text-right text-sm font-mono truncate min-w-0 max-w-[60%]',
valueClassName,
)}
title={typeof item.value === 'string' ? item.value : undefined}
title={stringValue}
>
{renderValue ? renderValue(item) : defaultRenderValue(item)}
</div>
</div>
))}
);
})}

{data.length === 0 && (
<div className="text-center text-muted-foreground py-8 col-span-full">
Expand Down
2 changes: 2 additions & 0 deletions apps/start/src/modals/event-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ function EventDetailsContent({ id, createdAt, projectId }: Props) {
{propertiesMode === 'table' && (
<KeyValueGrid
columns={1}
copyable
data={properties}
onItemClick={(item) => {
popModal();
Expand All @@ -310,6 +311,7 @@ function EventDetailsContent({ id, createdAt, projectId }: Props) {
</div>
<KeyValueGrid
columns={1}
copyable
data={data}
onItemClick={(item) => {
const isFilterable = item.value && (filterable as any)[item.name];
Expand Down