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 cellsStyle - The style of the cells in the column
|
||||||
* @param extra - Extra data to pass to the render function
|
* @param extra - Extra data to pass to the render function
|
||||||
* @param noContext - Disable context menu for this column
|
* @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> = {
|
export type TableColumnProps<T = any> = {
|
||||||
accessor?: string;
|
accessor?: string;
|
||||||
@@ -123,6 +125,8 @@ export type TableColumnProps<T = any> = {
|
|||||||
extra?: any;
|
extra?: any;
|
||||||
noContext?: boolean;
|
noContext?: boolean;
|
||||||
style?: MantineStyleProp;
|
style?: MantineStyleProp;
|
||||||
|
copyable?: boolean | ((record: T) => string);
|
||||||
|
copyAccessor?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -46,7 +46,11 @@ export function CopyButton({
|
|||||||
<ButtonComponent
|
<ButtonComponent
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
color={copied ? 'teal' : color}
|
color={copied ? 'teal' : color}
|
||||||
onClick={copy}
|
onClick={(e: any) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
copy();
|
||||||
|
}}
|
||||||
variant='transparent'
|
variant='transparent'
|
||||||
size={size ?? 'sm'}
|
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 { useLocalState } from '../states/LocalState';
|
||||||
import { useUserSettingsState } from '../states/SettingsStates';
|
import { useUserSettingsState } from '../states/SettingsStates';
|
||||||
import { useStoredTableState } from '../states/StoredTableState';
|
import { useStoredTableState } from '../states/StoredTableState';
|
||||||
|
import { CopyableCell } from './CopyableCell';
|
||||||
import InvenTreeTableHeader from './InvenTreeTableHeader';
|
import InvenTreeTableHeader from './InvenTreeTableHeader';
|
||||||
|
|
||||||
const ACTIONS_COLUMN_ACCESSOR: string = '--actions--';
|
const ACTIONS_COLUMN_ACCESSOR: string = '--actions--';
|
||||||
@@ -264,11 +265,36 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
? false
|
? false
|
||||||
: (tableState.hiddenColumns?.includes(col.accessor) ?? 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 {
|
return {
|
||||||
...col,
|
...col,
|
||||||
hidden: hidden,
|
hidden: hidden,
|
||||||
resizable: col.resizable ?? true,
|
resizable: col.resizable ?? true,
|
||||||
title: col.title ?? fieldNames[col.accessor] ?? `${col.accessor}`,
|
title: col.title ?? fieldNames[col.accessor] ?? `${col.accessor}`,
|
||||||
|
render: wrappedRender,
|
||||||
cellsStyle: (record: any, index: number) => {
|
cellsStyle: (record: any, index: number) => {
|
||||||
const width = (col as any).minWidth ?? 100;
|
const width = (col as any).minWidth ?? 100;
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -237,7 +237,8 @@ export function PurchaseOrderLineItemTable({
|
|||||||
title: t`Supplier Code`,
|
title: t`Supplier Code`,
|
||||||
switchable: false,
|
switchable: false,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
ordering: 'SKU'
|
ordering: 'SKU',
|
||||||
|
copyable: true
|
||||||
},
|
},
|
||||||
LinkColumn({
|
LinkColumn({
|
||||||
accessor: 'supplier_part_detail.link',
|
accessor: 'supplier_part_detail.link',
|
||||||
|
|||||||
Reference in New Issue
Block a user