mirror of
https://github.com/inventree/InvenTree.git
synced 2026-02-25 16:17:58 +00:00
feat: add copyable cells feature with CopyButton component (#11406)
This commit is contained in:
@@ -99,6 +99,8 @@ export type TableState = {
|
||||
* @param cellsStyle - The style of the cells in the column
|
||||
* @param extra - Extra data to pass to the render function
|
||||
* @param noContext - Disable context menu for this column
|
||||
* @param copyable - Enable copy button on hover (uses accessor to get value, or custom function)
|
||||
* @param copyAccessor - Custom accessor path for copy value (defaults to column accessor)
|
||||
*/
|
||||
export type TableColumnProps<T = any> = {
|
||||
accessor?: string;
|
||||
@@ -123,6 +125,8 @@ export type TableColumnProps<T = any> = {
|
||||
extra?: any;
|
||||
noContext?: boolean;
|
||||
style?: MantineStyleProp;
|
||||
copyable?: boolean | ((record: T) => string);
|
||||
copyAccessor?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,7 +46,11 @@ export function CopyButton({
|
||||
<ButtonComponent
|
||||
disabled={disabled}
|
||||
color={copied ? 'teal' : color}
|
||||
onClick={copy}
|
||||
onClick={(e: any) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
copy();
|
||||
}}
|
||||
variant='transparent'
|
||||
size={size ?? 'sm'}
|
||||
>
|
||||
|
||||
40
src/frontend/src/tables/CopyableCell.tsx
Normal file
40
src/frontend/src/tables/CopyableCell.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Group } from '@mantine/core';
|
||||
import { useState } from 'react';
|
||||
import { CopyButton } from '../components/buttons/CopyButton';
|
||||
|
||||
/**
|
||||
* A wrapper component that adds a copy button to cell content on hover
|
||||
* This component is used to make table cells copyable without adding visual clutter
|
||||
*
|
||||
* @param children - The cell content to render
|
||||
* @param value - The value to copy when the copy button is clicked
|
||||
*/
|
||||
export function CopyableCell({
|
||||
children,
|
||||
value
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
value: string;
|
||||
}>) {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
return (
|
||||
<Group
|
||||
gap='xs'
|
||||
wrap='nowrap'
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
justify='space-between'
|
||||
>
|
||||
{children}
|
||||
{isHovered && value != null && (
|
||||
<span
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<CopyButton value={value} />
|
||||
</span>
|
||||
)}
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import { hashString } from '../functions/tables';
|
||||
import { useLocalState } from '../states/LocalState';
|
||||
import { useUserSettingsState } from '../states/SettingsStates';
|
||||
import { useStoredTableState } from '../states/StoredTableState';
|
||||
import { CopyableCell } from './CopyableCell';
|
||||
import InvenTreeTableHeader from './InvenTreeTableHeader';
|
||||
|
||||
const ACTIONS_COLUMN_ACCESSOR: string = '--actions--';
|
||||
@@ -264,11 +265,36 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
||||
? false
|
||||
: (tableState.hiddenColumns?.includes(col.accessor) ?? false);
|
||||
|
||||
// Wrap the render function with CopyableCell if copyable is enabled
|
||||
const originalRender = col.render;
|
||||
let wrappedRender = originalRender;
|
||||
|
||||
if (col.copyable) {
|
||||
wrappedRender = (record: any, index?: number) => {
|
||||
const content =
|
||||
originalRender?.(record, index) ??
|
||||
resolveItem(record, col.accessor);
|
||||
|
||||
// Determine the value to copy, ensuring it is always a string
|
||||
let rawCopyValue: unknown;
|
||||
if (typeof col.copyable === 'function') {
|
||||
rawCopyValue = col.copyable(record);
|
||||
} else {
|
||||
const accessor = col.copyAccessor ?? col.accessor;
|
||||
rawCopyValue = resolveItem(record, accessor);
|
||||
}
|
||||
const copyValue = rawCopyValue == null ? '' : String(rawCopyValue);
|
||||
|
||||
return <CopyableCell value={copyValue}>{content}</CopyableCell>;
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...col,
|
||||
hidden: hidden,
|
||||
resizable: col.resizable ?? true,
|
||||
title: col.title ?? fieldNames[col.accessor] ?? `${col.accessor}`,
|
||||
render: wrappedRender,
|
||||
cellsStyle: (record: any, index: number) => {
|
||||
const width = (col as any).minWidth ?? 100;
|
||||
return {
|
||||
|
||||
@@ -237,7 +237,8 @@ export function PurchaseOrderLineItemTable({
|
||||
title: t`Supplier Code`,
|
||||
switchable: false,
|
||||
sortable: true,
|
||||
ordering: 'SKU'
|
||||
ordering: 'SKU',
|
||||
copyable: true
|
||||
},
|
||||
LinkColumn({
|
||||
accessor: 'supplier_part_detail.link',
|
||||
|
||||
Reference in New Issue
Block a user