Skip to content
Closed
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
53 changes: 50 additions & 3 deletions src/components/TableWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
Td,
Caption,
} from "@patternfly/react-table";
import { CopyIcon, CheckIcon } from "@patternfly/react-icons";
import React, { useState } from "react";

import ErrorPlaceholder from "./ErrorPlaceholder";

Expand All @@ -26,6 +28,33 @@ interface TableWrapperProps {
onRowClick?: (rowData: Record<string, string | number | null>) => void;
}

// Copy button component with visual feedback
const CopyButton: React.FC<{ text: string }> = ({ text }) => {
const [copied, setCopied] = useState(false);

const handleCopy = async (e: React.MouseEvent) => {
e.stopPropagation(); // Prevent row click
try {
await navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error('Failed to copy:', err);
}
};

return (
<button
onClick={handleCopy}
className="copy-button"
title={copied ? 'Copied!' : 'Copy to clipboard'}
aria-label={copied ? 'Copied' : 'Copy to clipboard'}
>
{copied ? <CheckIcon /> : <CopyIcon />}
</button>
);
};

const TableWrapper = (props: TableWrapperProps) => {
const { title, id, fields, className, onRowClick } = props;

Expand Down Expand Up @@ -110,9 +139,27 @@ const TableWrapper = (props: TableWrapperProps) => {
style={onRowClick ? { cursor: 'pointer' } : undefined}
isHoverable={!!onRowClick}
>
{columns.map((col, colIndex) => (
<Td key={colIndex}>{row[col.key]}</Td>
))}
{columns.map((col, colIndex) => {
const cellValue = row[col.key];

// Add copy button for ID, Name, URL, Email columns
const isCopyableColumn = ['id', 'name', 'url', 'email', 'cluster'].some(
keyword => col.key.toLowerCase().includes(keyword)
);

return (
<Td key={colIndex}>
{isCopyableColumn ? (
<span className="cell-with-copy">
<span className="cell-value">{cellValue}</span>
<CopyButton text={String(cellValue)} />
</span>
) : (
cellValue
)}
</Td>
);
})}
</Tr>
))}
</Tbody>
Expand Down
61 changes: 61 additions & 0 deletions src/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,64 @@
color: var(--pf-global--Color--danger-200);
border: 1px solid var(--pf-global--Color--danger-200);
}

/* Copy Button Styles */
.cell-with-copy {
display: inline-flex;
align-items: center;
gap: 0.5rem;
width: 100%;
}

.cell-value {
flex: 1;
}

/* Copy button - hidden by default */
.copy-button {
opacity: 0;
background: transparent;
border: none;
padding: 0.25rem 0.5rem;
cursor: pointer;
font-size: 1rem;
line-height: 1;
transition: all 0.2s ease;
border-radius: 4px;
flex-shrink: 0;
color: #6b7280; /* PatternFly neutral gray */
}

.copy-button:hover {
background: #f3f4f6;
transform: scale(1.1);
color: #374151;
}

.copy-button:active {
transform: scale(0.95);
}

/* Show copy button on row hover */
.pf-v6-c-table tbody tr:hover .copy-button {
opacity: 1;
}

/* When copied, always show and make it green */
.copy-button[title="Copied!"] {
opacity: 1 !important;
color: #10b981;
animation: copySuccess 0.3s ease;
}

@keyframes copySuccess {
0% {
transform: scale(1);
}
50% {
transform: scale(1.3);
}
100% {
transform: scale(1);
}
}
Loading