Fix row highlighting, header alignment, make header sticky
This commit is contained in:
@@ -249,7 +249,7 @@ const ItemNumberCell = React.memo(({
|
||||
);
|
||||
|
||||
return (
|
||||
<TableCell className="p-1 group relative" style={{ width: `${width}px`, minWidth: `${width}px` }}>
|
||||
<TableCell className="p-1 group relative" style={{ width: `${width}px`, minWidth: `${width}px`, maxWidth: `${width}px`, boxSizing: 'border-box' }}>
|
||||
<div className={`relative ${hasError || isRequiredButEmpty ? 'border-red-500' : ''}`}>
|
||||
{isValidating ? (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
@@ -365,7 +365,7 @@ const ValidationCell = ({
|
||||
// Check for price field
|
||||
|
||||
return (
|
||||
<TableCell className="p-1 group relative" style={{ width: `${width}px`, minWidth: `${width}px` }}>
|
||||
<TableCell className="p-1 group relative" style={{ width: `${width}px`, minWidth: `${width}px`, maxWidth: `${width}px`, boxSizing: 'border-box' }}>
|
||||
<div className={`relative ${hasError || isRequiredButEmpty ? 'border-red-500' : ''}`}>
|
||||
{isValidating ? (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Fields } from '../../../types'
|
||||
import { SearchProductTemplateDialog } from '@/components/templates/SearchProductTemplateDialog'
|
||||
import { TemplateForm } from '@/components/templates/TemplateForm'
|
||||
import axios from 'axios'
|
||||
import { RowSelectionState } from '@tanstack/react-table'
|
||||
|
||||
/**
|
||||
* ValidationContainer component - the main wrapper for the validation step
|
||||
@@ -56,13 +57,15 @@ const ValidationContainer = <T extends string>({
|
||||
loadTemplates,
|
||||
setData,
|
||||
fields,
|
||||
isLoadingTemplates,
|
||||
copyDown } = validationState
|
||||
isLoadingTemplates } = validationState
|
||||
|
||||
// Add state for tracking product lines and sublines per row
|
||||
const [rowProductLines, setRowProductLines] = useState<Record<string, any[]>>({});
|
||||
const [rowSublines, setRowSublines] = useState<Record<string, any[]>>({});
|
||||
// These variables are used in the fetchProductLines and fetchSublines functions
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [isLoadingLines, setIsLoadingLines] = useState<Record<string, boolean>>({});
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [isLoadingSublines, setIsLoadingSublines] = useState<Record<string, boolean>>({});
|
||||
|
||||
// Add UPC validation state
|
||||
@@ -432,7 +435,10 @@ const ValidationContainer = <T extends string>({
|
||||
if (rowData && rowData.__index) {
|
||||
// Use setTimeout to make this non-blocking
|
||||
setTimeout(async () => {
|
||||
await fetchProductLines(rowData.__index, value.toString());
|
||||
// Ensure value is not undefined before calling toString()
|
||||
if (value !== undefined) {
|
||||
await fetchProductLines(rowData.__index as string, value.toString());
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
@@ -494,7 +500,10 @@ const ValidationContainer = <T extends string>({
|
||||
if (rowData && rowData.__index) {
|
||||
// Use setTimeout to make this non-blocking
|
||||
setTimeout(async () => {
|
||||
await fetchSublines(rowData.__index, value.toString());
|
||||
// Ensure value is not undefined before calling toString()
|
||||
if (value !== undefined) {
|
||||
await fetchSublines(rowData.__index as string, value.toString());
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
@@ -711,7 +720,7 @@ const ValidationContainer = <T extends string>({
|
||||
// Log if we can find a match for our supplier
|
||||
if (templateData.supplier !== undefined) {
|
||||
// Need to compare numeric values since supplier options have numeric values
|
||||
const supplierMatch = options.suppliers.find(s =>
|
||||
const supplierMatch = options.suppliers.find((s: { value: string | number }) =>
|
||||
s.value === templateData.supplier ||
|
||||
Number(s.value) === Number(templateData.supplier)
|
||||
);
|
||||
@@ -814,9 +823,8 @@ const ValidationContainer = <T extends string>({
|
||||
}, [data, rowSelection, setData, setRowSelection]);
|
||||
|
||||
// Memoize handlers
|
||||
const handleFiltersChange = useCallback((newFilters: any) => {
|
||||
updateFilters(newFilters);
|
||||
}, [updateFilters]);
|
||||
// This function is defined for potential future use but not currently used
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
|
||||
const handleRowSelectionChange = useCallback((newSelection: RowSelectionState) => {
|
||||
setRowSelection(newSelection);
|
||||
@@ -883,10 +891,10 @@ const ValidationContainer = <T extends string>({
|
||||
const renderValidationTable = useMemo(() => (
|
||||
<EnhancedValidationTable
|
||||
data={filteredData}
|
||||
fields={fields}
|
||||
fields={fields as unknown as Fields<string>}
|
||||
rowSelection={rowSelection}
|
||||
setRowSelection={handleRowSelectionChange}
|
||||
updateRow={handleUpdateRow}
|
||||
setRowSelection={handleRowSelectionChange as React.Dispatch<React.SetStateAction<RowSelectionState>>}
|
||||
updateRow={handleUpdateRow as (rowIndex: number, key: string, value: any) => void}
|
||||
validationErrors={validationErrors}
|
||||
isValidatingUpc={isRowValidatingUpc}
|
||||
validatingUpcRows={Array.from(validatingUpcRows)}
|
||||
@@ -898,6 +906,7 @@ const ValidationContainer = <T extends string>({
|
||||
itemNumbers={new Map()}
|
||||
isLoadingTemplates={isLoadingTemplates}
|
||||
copyDown={handleCopyDown}
|
||||
upcValidationResults={new Map()}
|
||||
/>
|
||||
), [
|
||||
EnhancedValidationTable,
|
||||
@@ -923,10 +932,11 @@ const ValidationContainer = <T extends string>({
|
||||
const isScrolling = useRef(false);
|
||||
|
||||
// Memoize scroll handlers
|
||||
const handleScroll = useCallback((event: React.UIEvent<HTMLDivElement>) => {
|
||||
const handleScroll = useCallback((event: React.UIEvent<HTMLDivElement> | Event) => {
|
||||
if (!isScrolling.current) {
|
||||
isScrolling.current = true;
|
||||
const target = event.currentTarget;
|
||||
// Use type assertion to handle both React.UIEvent and native Event
|
||||
const target = event.currentTarget as HTMLDivElement;
|
||||
lastScrollPosition.current = {
|
||||
left: target.scrollLeft,
|
||||
top: target.scrollTop
|
||||
@@ -941,8 +951,13 @@ const ValidationContainer = <T extends string>({
|
||||
useEffect(() => {
|
||||
const container = scrollContainerRef.current;
|
||||
if (container) {
|
||||
container.addEventListener('scroll', handleScroll, { passive: true });
|
||||
return () => container.removeEventListener('scroll', handleScroll);
|
||||
// Convert React event handler to native event handler
|
||||
const nativeHandler = ((evt: Event) => {
|
||||
handleScroll(evt);
|
||||
}) as EventListener;
|
||||
|
||||
container.addEventListener('scroll', nativeHandler, { passive: true });
|
||||
return () => container.removeEventListener('scroll', nativeHandler);
|
||||
}
|
||||
}, [handleScroll]);
|
||||
|
||||
@@ -1031,13 +1046,14 @@ const ValidationContainer = <T extends string>({
|
||||
style={{
|
||||
willChange: 'transform',
|
||||
position: 'relative',
|
||||
WebkitOverflowScrolling: 'touch' // Improve scroll performance on Safari
|
||||
WebkitOverflowScrolling: 'touch', // Improve scroll performance on Safari
|
||||
overscrollBehavior: 'contain', // Prevent scroll chaining
|
||||
contain: 'paint', // Improve performance for sticky elements
|
||||
scrollbarWidth: 'thin' // Thinner scrollbars in Firefox
|
||||
}}
|
||||
onScroll={handleScroll}
|
||||
>
|
||||
<div className="min-w-max"> {/* Force container to be at least as wide as content */}
|
||||
{renderValidationTable}
|
||||
</div>
|
||||
{renderValidationTable}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -217,7 +217,7 @@ const ValidationTable = <T extends string>({
|
||||
const rowIndex = data.findIndex(r => r === row.original);
|
||||
|
||||
return (
|
||||
<TableCell className="p-1" style={{ width: '200px', minWidth: '200px' }}>
|
||||
<TableCell className="p-1" style={{ width: '200px', minWidth: '200px', maxWidth: '200px' }}>
|
||||
<MemoizedTemplateSelect
|
||||
templates={templates}
|
||||
value={templateValue || ''}
|
||||
@@ -280,9 +280,9 @@ const ValidationTable = <T extends string>({
|
||||
size: fieldWidth,
|
||||
cell: ({ row }) => (
|
||||
<MemoizedCell
|
||||
field={field}
|
||||
value={row.original[field.key]}
|
||||
onChange={(value) => handleFieldUpdate(row.index, field.key, value)}
|
||||
field={field as Field<string>}
|
||||
value={row.original[field.key as keyof typeof row.original]}
|
||||
onChange={(value) => handleFieldUpdate(row.index, field.key as T, value)}
|
||||
errors={validationErrors.get(row.index)?.[fieldKey] || []}
|
||||
isValidating={validatingCells.has(`${row.index}-${field.key}`)}
|
||||
fieldKey={fieldKey}
|
||||
@@ -290,7 +290,7 @@ const ValidationTable = <T extends string>({
|
||||
itemNumber={itemNumbers.get(row.index)}
|
||||
width={fieldWidth}
|
||||
rowIndex={row.index}
|
||||
copyDown={() => handleCopyDown(row.index, field.key)}
|
||||
copyDown={() => handleCopyDown(row.index, field.key as string)}
|
||||
/>
|
||||
)
|
||||
};
|
||||
@@ -333,53 +333,71 @@ const ValidationTable = <T extends string>({
|
||||
}
|
||||
|
||||
return (
|
||||
<Table style={{ width: `${totalWidth}px`, tableLayout: 'fixed' }}>
|
||||
<TableHeader className="sticky top-0 z-10 bg-background">
|
||||
<TableRow>
|
||||
{table.getFlatHeaders().map((header) => (
|
||||
<TableHead
|
||||
key={header.id}
|
||||
style={{
|
||||
width: `${header.getSize()}px`,
|
||||
minWidth: `${header.getSize()}px`,
|
||||
maxWidth: `${header.getSize()}px`,
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
backgroundColor: 'inherit',
|
||||
zIndex: 1
|
||||
}}
|
||||
>
|
||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
className={cn(
|
||||
"hover:bg-muted/50",
|
||||
row.getIsSelected() ? "bg-muted/50" : "",
|
||||
validationErrors.get(data.indexOf(row.original)) ? "bg-red-50/40" : ""
|
||||
)}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell
|
||||
key={cell.id}
|
||||
style={{
|
||||
width: `${cell.column.getSize()}px`,
|
||||
minWidth: `${cell.column.getSize()}px`,
|
||||
maxWidth: `${cell.column.getSize()}px`
|
||||
}}
|
||||
<div className="min-w-max">
|
||||
<div className="relative">
|
||||
{/* Custom Table Header - Always Visible */}
|
||||
<div
|
||||
className="sticky top-0 z-20 bg-muted border-b shadow-sm"
|
||||
style={{ width: `${totalWidth}px` }}
|
||||
>
|
||||
<div className="flex">
|
||||
{table.getFlatHeaders().map((header, index) => {
|
||||
const width = header.getSize();
|
||||
return (
|
||||
<div
|
||||
key={header.id}
|
||||
className="py-2 px-2 font-bold text-sm text-muted-foreground bg-muted flex items-center justify-center"
|
||||
style={{
|
||||
width: `${width}px`,
|
||||
minWidth: `${width}px`,
|
||||
maxWidth: `${width}px`,
|
||||
boxSizing: 'border-box',
|
||||
height: '40px'
|
||||
}}
|
||||
>
|
||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table Body */}
|
||||
<Table style={{ width: `${totalWidth}px`, tableLayout: 'fixed', borderCollapse: 'separate', borderSpacing: 0, marginTop: '-1px' }}>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
className={cn(
|
||||
"hover:bg-muted/50",
|
||||
row.getIsSelected() ? "bg-muted/50" : "",
|
||||
validationErrors.get(data.indexOf(row.original)) &&
|
||||
Object.keys(validationErrors.get(data.indexOf(row.original)) || {}).length > 0 ? "bg-red-50/40" : ""
|
||||
)}
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
{row.getVisibleCells().map((cell, cellIndex) => {
|
||||
const width = cell.column.getSize();
|
||||
return (
|
||||
<TableCell
|
||||
key={cell.id}
|
||||
style={{
|
||||
width: `${width}px`,
|
||||
minWidth: `${width}px`,
|
||||
maxWidth: `${width}px`,
|
||||
boxSizing: 'border-box',
|
||||
padding: '0'
|
||||
}}
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ interface MultiInputCellProps<T extends string> {
|
||||
}
|
||||
|
||||
// Add global CSS to ensure fixed width constraints - use !important to override other styles
|
||||
const fixedWidthClass = "!w-full !min-w-0 !max-w-full !flex-shrink-1 !flex-grow-0";
|
||||
|
||||
// Memoized option item to prevent unnecessary renders for large option lists
|
||||
const OptionItem = React.memo(({
|
||||
@@ -372,133 +371,91 @@ const MultiInputCell = <T extends string>({
|
||||
|
||||
// If we have a multi-select field with options, use command UI
|
||||
if (field.fieldType.type === 'multi-select' && selectOptions.length > 0) {
|
||||
// Get width from field if available, or default to a reasonable value
|
||||
const cellWidth = field.width || 200;
|
||||
|
||||
// Create a reference to the container element
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Create a key-value map for inline styles with fixed width - simplified
|
||||
const fixedWidth = useMemo(() => ({
|
||||
width: `${cellWidth}px`,
|
||||
minWidth: `${cellWidth}px`,
|
||||
maxWidth: `${cellWidth}px`,
|
||||
boxSizing: 'border-box' as const,
|
||||
}), [cellWidth]);
|
||||
|
||||
// Use layout effect more efficiently - only for the button element
|
||||
// since the container already uses inline styles
|
||||
useLayoutEffect(() => {
|
||||
// Skip if no width specified
|
||||
if (!cellWidth) return;
|
||||
|
||||
// Cache previous width to avoid unnecessary DOM updates
|
||||
const prevWidth = containerRef.current?.getAttribute('data-prev-width');
|
||||
|
||||
// Only update if width changed
|
||||
if (prevWidth !== String(cellWidth) && containerRef.current) {
|
||||
// Store new width for next comparison
|
||||
containerRef.current.setAttribute('data-prev-width', String(cellWidth));
|
||||
|
||||
// Only manipulate the button element directly since we can't
|
||||
// reliably style it with CSS in all cases
|
||||
const button = containerRef.current.querySelector('button');
|
||||
if (button) {
|
||||
const htmlButton = button as HTMLElement;
|
||||
htmlButton.style.width = `${cellWidth}px`;
|
||||
htmlButton.style.minWidth = `${cellWidth}px`;
|
||||
htmlButton.style.maxWidth = `${cellWidth}px`;
|
||||
}
|
||||
}
|
||||
}, [cellWidth]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="inline-block fixed-width-cell overflow-visible"
|
||||
style={fixedWidth}
|
||||
data-width={cellWidth}
|
||||
<Popover
|
||||
open={open}
|
||||
onOpenChange={(isOpen) => {
|
||||
setOpen(isOpen);
|
||||
handleOpenChange(isOpen);
|
||||
}}
|
||||
>
|
||||
<Popover open={open} onOpenChange={handleOpenChange}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className={cn(
|
||||
"justify-between font-normal",
|
||||
!internalValue.length && "text-muted-foreground",
|
||||
hasErrors && "border-red-500",
|
||||
"h-auto min-h-9 py-1"
|
||||
)}
|
||||
onClick={() => setOpen(true)}
|
||||
style={fixedWidth}
|
||||
>
|
||||
<div className="flex items-center w-full justify-between">
|
||||
<div
|
||||
className="flex items-center gap-2 overflow-hidden"
|
||||
style={{
|
||||
maxWidth: `${cellWidth - 32}px`,
|
||||
}}
|
||||
>
|
||||
{internalValue.length === 0 ? (
|
||||
<span className="text-muted-foreground truncate w-full">Select...</span>
|
||||
) : internalValue.length === 1 ? (
|
||||
<span className="truncate w-full">{selectedValues[0].label}</span>
|
||||
) : (
|
||||
<>
|
||||
<Badge variant="secondary" className="shrink-0 whitespace-nowrap">
|
||||
{internalValue.length} selected
|
||||
</Badge>
|
||||
<span className="truncate" style={{ maxWidth: `${cellWidth - 100}px` }}>
|
||||
{selectedValues.map(v => v.label).join(', ')}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="ml-1 flex-none" style={{ width: '20px' }}>
|
||||
<ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="p-0"
|
||||
style={fixedWidth}
|
||||
align="start"
|
||||
sideOffset={4}
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className={cn(
|
||||
"w-full justify-between font-normal",
|
||||
"border",
|
||||
!internalValue.length && "text-muted-foreground",
|
||||
hasErrors ? "border-destructive" : ""
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setOpen(!open);
|
||||
if (!open && onStartEdit) onStartEdit();
|
||||
}}
|
||||
>
|
||||
<Command shouldFilter={false} className="overflow-hidden">
|
||||
<CommandInput
|
||||
placeholder="Search options..."
|
||||
className="h-9"
|
||||
value={searchQuery}
|
||||
onValueChange={setSearchQuery}
|
||||
/>
|
||||
<CommandList
|
||||
className="overflow-hidden"
|
||||
ref={commandListRef}
|
||||
onWheel={handleWheel}
|
||||
>
|
||||
<CommandEmpty>No options found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{sortedOptions.length > 0 ? (
|
||||
<VirtualizedOptions
|
||||
options={sortedOptions}
|
||||
selectedValues={selectedValueSet}
|
||||
onSelect={handleSelect}
|
||||
maxHeight={200}
|
||||
/>
|
||||
) : (
|
||||
<div className="py-6 text-center text-sm">No options match your search</div>
|
||||
)}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
<div className="flex items-center w-full justify-between">
|
||||
<div className="flex items-center gap-2 overflow-hidden">
|
||||
{internalValue.length === 0 ? (
|
||||
<span className="text-muted-foreground truncate w-full">Select...</span>
|
||||
) : internalValue.length === 1 ? (
|
||||
<span className="truncate w-full">{selectedValues[0].label}</span>
|
||||
) : (
|
||||
<>
|
||||
<Badge variant="secondary" className="shrink-0 whitespace-nowrap">
|
||||
{internalValue.length} selected
|
||||
</Badge>
|
||||
<span className="truncate">
|
||||
{selectedValues.map(v => v.label).join(', ')}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</div>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="p-0 w-[var(--radix-popover-trigger-width)]"
|
||||
align="start"
|
||||
sideOffset={4}
|
||||
>
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
placeholder="Search..."
|
||||
className="h-9"
|
||||
value={searchQuery}
|
||||
onValueChange={setSearchQuery}
|
||||
/>
|
||||
<CommandList
|
||||
ref={commandListRef}
|
||||
onWheel={handleWheel}
|
||||
className="max-h-[200px]"
|
||||
>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{sortedOptions.map((option) => (
|
||||
<CommandItem
|
||||
key={option.value}
|
||||
value={option.label}
|
||||
onSelect={() => handleSelect(option.value)}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
{option.label}
|
||||
{selectedValueSet.has(option.value) && (
|
||||
<Check className="ml-auto h-4 w-4" />
|
||||
)}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
// For standard multi-input without options, use text input
|
||||
@@ -510,12 +467,6 @@ const MultiInputCell = <T extends string>({
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Create a key-value map for inline styles with fixed width - simplified
|
||||
const fixedWidth = useMemo(() => ({
|
||||
width: `${cellWidth}px`,
|
||||
minWidth: `${cellWidth}px`,
|
||||
maxWidth: `${cellWidth}px`,
|
||||
boxSizing: 'border-box' as const,
|
||||
}), [cellWidth]);
|
||||
|
||||
// Use layout effect more efficiently - only for the button element
|
||||
// since the container already uses inline styles
|
||||
@@ -539,6 +490,7 @@ const MultiInputCell = <T extends string>({
|
||||
htmlButton.style.width = `${cellWidth}px`;
|
||||
htmlButton.style.minWidth = `${cellWidth}px`;
|
||||
htmlButton.style.maxWidth = `${cellWidth}px`;
|
||||
htmlButton.style.boxSizing = 'border-box';
|
||||
}
|
||||
}
|
||||
}, [cellWidth]);
|
||||
@@ -547,7 +499,12 @@ const MultiInputCell = <T extends string>({
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="inline-block fixed-width-cell"
|
||||
style={fixedWidth}
|
||||
style={{
|
||||
width: `${cellWidth}px`,
|
||||
minWidth: `${cellWidth}px`,
|
||||
maxWidth: `${cellWidth}px`,
|
||||
boxSizing: 'border-box',
|
||||
}}
|
||||
data-width={cellWidth}
|
||||
>
|
||||
{isMultiline ? (
|
||||
@@ -562,7 +519,12 @@ const MultiInputCell = <T extends string>({
|
||||
outlineClass,
|
||||
hasErrors ? "border-destructive" : ""
|
||||
)}
|
||||
style={fixedWidth}
|
||||
style={{
|
||||
width: `${cellWidth}px`,
|
||||
minWidth: `${cellWidth}px`,
|
||||
maxWidth: `${cellWidth}px`,
|
||||
boxSizing: 'border-box',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
@@ -571,10 +533,16 @@ const MultiInputCell = <T extends string>({
|
||||
"cursor-text truncate",
|
||||
outlineClass,
|
||||
hasErrors ? "border-destructive" : "",
|
||||
"overflow-hidden items-center"
|
||||
"overflow-hidden items-center",
|
||||
"w-full"
|
||||
)}
|
||||
onClick={handleFocus}
|
||||
style={fixedWidth}
|
||||
style={{
|
||||
width: `${cellWidth}px`,
|
||||
minWidth: `${cellWidth}px`,
|
||||
maxWidth: `${cellWidth}px`,
|
||||
boxSizing: 'border-box',
|
||||
}}
|
||||
>
|
||||
{internalValue.length > 0 ? getDisplayValues().join(`, `) : (
|
||||
<span className="text-muted-foreground truncate">
|
||||
@@ -589,7 +557,7 @@ const MultiInputCell = <T extends string>({
|
||||
|
||||
// Fallback to default behavior if no width is specified
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="w-full overflow-hidden" style={{ boxSizing: 'border-box' }}>
|
||||
{isMultiline ? (
|
||||
<Textarea
|
||||
value={internalValue.join(separator)}
|
||||
@@ -598,10 +566,11 @@ const MultiInputCell = <T extends string>({
|
||||
onBlur={handleBlur}
|
||||
placeholder={`Enter values separated by ${separator}`}
|
||||
className={cn(
|
||||
"min-h-[80px] resize-none",
|
||||
"min-h-[80px] resize-none w-full",
|
||||
outlineClass,
|
||||
hasErrors ? "border-destructive" : ""
|
||||
)}
|
||||
style={{ boxSizing: 'border-box' }}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
@@ -613,9 +582,10 @@ const MultiInputCell = <T extends string>({
|
||||
"overflow-hidden items-center"
|
||||
)}
|
||||
onClick={handleFocus}
|
||||
style={{ boxSizing: 'border-box' }}
|
||||
>
|
||||
{internalValue.length > 0 ? getDisplayValues().join(`, `) : (
|
||||
<span className="text-muted-foreground">
|
||||
<span className="text-muted-foreground truncate w-full">
|
||||
{`Enter values separated by ${separator}`}
|
||||
</span>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user