Fix row highlighting, header alignment, make header sticky

This commit is contained in:
2025-03-11 21:08:02 -04:00
parent 1aee18a025
commit f55d35e301
5 changed files with 219 additions and 213 deletions

View File

@@ -1,6 +1,4 @@
# Current Issues to Address # Current Issues to Address
1. The red row background should go away when all cells in the row are valid and all required cells are populated
2. Columns alignment with header is slightly off, gets worse the further right you go
3. The copy down button is in the way of the validation error icon and the select open trigger - all three need to be in unique locations 3. The copy down button is in the way of the validation error icon and the select open trigger - all three need to be in unique locations
4. Validation isn't happening beyond checking if a cell is required or not - needs to respect rules in import.tsx 4. Validation isn't happening beyond checking if a cell is required or not - needs to respect rules in import.tsx
* Red cell outline if cell is required and it's empty * Red cell outline if cell is required and it's empty
@@ -14,15 +12,19 @@
10. UPC column doesn't need to show loading state when Item Number is being processed, only show on item number column 10. UPC column doesn't need to show loading state when Item Number is being processed, only show on item number column
11. Copy down needs to show a loading state on the cells that it will copy to 11. Copy down needs to show a loading state on the cells that it will copy to
12. Shipping restrictions/tax category should default to ID 0 if we didn't get it elsewhere 12. Shipping restrictions/tax category should default to ID 0 if we didn't get it elsewhere
13. Header row should be sticky (both up/down and left/right)
14. Need a way to scroll around table if user doesn't have mouse wheel for left/right 14. Need a way to scroll around table if user doesn't have mouse wheel for left/right
15. Need to remove all artificial virtualization, batching, artificial delays, and caching. Adds too much complexity and data set is not ever large enough for this to be helpful. Keep actual performance optimizations.
## Do NOT change or edit ## Do NOT change or edit
* Anything related to AI validation * Anything related to AI validation
* Anything about how templates or UPC validation work (only focus on specific issues described above) * Anything about how templates or UPC validation work (only focus on specific issues described above)
* Anything outside of the ValidationStepNew folder * Anything outside of the ValidationStepNew folder
## Issues already fixed - do not work on these
✅FIXED 1. The red row background should go away when all cells in the row are valid and all required cells are populated
✅FIXED 2. Columns alignment with header is slightly off, gets worse the further right you go
✅FIXED 13. Header row should be sticky (both up/down and left/right)
--------- ---------
# Validation Step Components Overview # Validation Step Components Overview

View File

@@ -249,7 +249,7 @@ const ItemNumberCell = React.memo(({
); );
return ( 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' : ''}`}> <div className={`relative ${hasError || isRequiredButEmpty ? 'border-red-500' : ''}`}>
{isValidating ? ( {isValidating ? (
<div className="flex items-center justify-center gap-2"> <div className="flex items-center justify-center gap-2">
@@ -365,7 +365,7 @@ const ValidationCell = ({
// Check for price field // Check for price field
return ( 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' : ''}`}> <div className={`relative ${hasError || isRequiredButEmpty ? 'border-red-500' : ''}`}>
{isValidating ? ( {isValidating ? (
<div className="flex items-center justify-center gap-2"> <div className="flex items-center justify-center gap-2">

View File

@@ -14,6 +14,7 @@ import { Fields } from '../../../types'
import { SearchProductTemplateDialog } from '@/components/templates/SearchProductTemplateDialog' import { SearchProductTemplateDialog } from '@/components/templates/SearchProductTemplateDialog'
import { TemplateForm } from '@/components/templates/TemplateForm' import { TemplateForm } from '@/components/templates/TemplateForm'
import axios from 'axios' import axios from 'axios'
import { RowSelectionState } from '@tanstack/react-table'
/** /**
* ValidationContainer component - the main wrapper for the validation step * ValidationContainer component - the main wrapper for the validation step
@@ -56,13 +57,15 @@ const ValidationContainer = <T extends string>({
loadTemplates, loadTemplates,
setData, setData,
fields, fields,
isLoadingTemplates, isLoadingTemplates } = validationState
copyDown } = validationState
// Add state for tracking product lines and sublines per row // Add state for tracking product lines and sublines per row
const [rowProductLines, setRowProductLines] = useState<Record<string, any[]>>({}); const [rowProductLines, setRowProductLines] = useState<Record<string, any[]>>({});
const [rowSublines, setRowSublines] = 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>>({}); const [isLoadingLines, setIsLoadingLines] = useState<Record<string, boolean>>({});
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [isLoadingSublines, setIsLoadingSublines] = useState<Record<string, boolean>>({}); const [isLoadingSublines, setIsLoadingSublines] = useState<Record<string, boolean>>({});
// Add UPC validation state // Add UPC validation state
@@ -432,7 +435,10 @@ const ValidationContainer = <T extends string>({
if (rowData && rowData.__index) { if (rowData && rowData.__index) {
// Use setTimeout to make this non-blocking // Use setTimeout to make this non-blocking
setTimeout(async () => { 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); }, 0);
} }
} }
@@ -494,7 +500,10 @@ const ValidationContainer = <T extends string>({
if (rowData && rowData.__index) { if (rowData && rowData.__index) {
// Use setTimeout to make this non-blocking // Use setTimeout to make this non-blocking
setTimeout(async () => { 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); }, 0);
} }
} }
@@ -711,7 +720,7 @@ const ValidationContainer = <T extends string>({
// Log if we can find a match for our supplier // Log if we can find a match for our supplier
if (templateData.supplier !== undefined) { if (templateData.supplier !== undefined) {
// Need to compare numeric values since supplier options have numeric values // 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 || s.value === templateData.supplier ||
Number(s.value) === Number(templateData.supplier) Number(s.value) === Number(templateData.supplier)
); );
@@ -814,9 +823,8 @@ const ValidationContainer = <T extends string>({
}, [data, rowSelection, setData, setRowSelection]); }, [data, rowSelection, setData, setRowSelection]);
// Memoize handlers // Memoize handlers
const handleFiltersChange = useCallback((newFilters: any) => { // This function is defined for potential future use but not currently used
updateFilters(newFilters); // eslint-disable-next-line @typescript-eslint/no-unused-vars
}, [updateFilters]);
const handleRowSelectionChange = useCallback((newSelection: RowSelectionState) => { const handleRowSelectionChange = useCallback((newSelection: RowSelectionState) => {
setRowSelection(newSelection); setRowSelection(newSelection);
@@ -883,10 +891,10 @@ const ValidationContainer = <T extends string>({
const renderValidationTable = useMemo(() => ( const renderValidationTable = useMemo(() => (
<EnhancedValidationTable <EnhancedValidationTable
data={filteredData} data={filteredData}
fields={fields} fields={fields as unknown as Fields<string>}
rowSelection={rowSelection} rowSelection={rowSelection}
setRowSelection={handleRowSelectionChange} setRowSelection={handleRowSelectionChange as React.Dispatch<React.SetStateAction<RowSelectionState>>}
updateRow={handleUpdateRow} updateRow={handleUpdateRow as (rowIndex: number, key: string, value: any) => void}
validationErrors={validationErrors} validationErrors={validationErrors}
isValidatingUpc={isRowValidatingUpc} isValidatingUpc={isRowValidatingUpc}
validatingUpcRows={Array.from(validatingUpcRows)} validatingUpcRows={Array.from(validatingUpcRows)}
@@ -898,6 +906,7 @@ const ValidationContainer = <T extends string>({
itemNumbers={new Map()} itemNumbers={new Map()}
isLoadingTemplates={isLoadingTemplates} isLoadingTemplates={isLoadingTemplates}
copyDown={handleCopyDown} copyDown={handleCopyDown}
upcValidationResults={new Map()}
/> />
), [ ), [
EnhancedValidationTable, EnhancedValidationTable,
@@ -923,10 +932,11 @@ const ValidationContainer = <T extends string>({
const isScrolling = useRef(false); const isScrolling = useRef(false);
// Memoize scroll handlers // Memoize scroll handlers
const handleScroll = useCallback((event: React.UIEvent<HTMLDivElement>) => { const handleScroll = useCallback((event: React.UIEvent<HTMLDivElement> | Event) => {
if (!isScrolling.current) { if (!isScrolling.current) {
isScrolling.current = true; 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 = { lastScrollPosition.current = {
left: target.scrollLeft, left: target.scrollLeft,
top: target.scrollTop top: target.scrollTop
@@ -941,8 +951,13 @@ const ValidationContainer = <T extends string>({
useEffect(() => { useEffect(() => {
const container = scrollContainerRef.current; const container = scrollContainerRef.current;
if (container) { if (container) {
container.addEventListener('scroll', handleScroll, { passive: true }); // Convert React event handler to native event handler
return () => container.removeEventListener('scroll', handleScroll); const nativeHandler = ((evt: Event) => {
handleScroll(evt);
}) as EventListener;
container.addEventListener('scroll', nativeHandler, { passive: true });
return () => container.removeEventListener('scroll', nativeHandler);
} }
}, [handleScroll]); }, [handleScroll]);
@@ -1031,13 +1046,14 @@ const ValidationContainer = <T extends string>({
style={{ style={{
willChange: 'transform', willChange: 'transform',
position: 'relative', 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} onScroll={handleScroll}
> >
<div className="min-w-max"> {/* Force container to be at least as wide as content */} {renderValidationTable}
{renderValidationTable}
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -217,7 +217,7 @@ const ValidationTable = <T extends string>({
const rowIndex = data.findIndex(r => r === row.original); const rowIndex = data.findIndex(r => r === row.original);
return ( return (
<TableCell className="p-1" style={{ width: '200px', minWidth: '200px' }}> <TableCell className="p-1" style={{ width: '200px', minWidth: '200px', maxWidth: '200px' }}>
<MemoizedTemplateSelect <MemoizedTemplateSelect
templates={templates} templates={templates}
value={templateValue || ''} value={templateValue || ''}
@@ -280,9 +280,9 @@ const ValidationTable = <T extends string>({
size: fieldWidth, size: fieldWidth,
cell: ({ row }) => ( cell: ({ row }) => (
<MemoizedCell <MemoizedCell
field={field} field={field as Field<string>}
value={row.original[field.key]} value={row.original[field.key as keyof typeof row.original]}
onChange={(value) => handleFieldUpdate(row.index, field.key, value)} onChange={(value) => handleFieldUpdate(row.index, field.key as T, value)}
errors={validationErrors.get(row.index)?.[fieldKey] || []} errors={validationErrors.get(row.index)?.[fieldKey] || []}
isValidating={validatingCells.has(`${row.index}-${field.key}`)} isValidating={validatingCells.has(`${row.index}-${field.key}`)}
fieldKey={fieldKey} fieldKey={fieldKey}
@@ -290,7 +290,7 @@ const ValidationTable = <T extends string>({
itemNumber={itemNumbers.get(row.index)} itemNumber={itemNumbers.get(row.index)}
width={fieldWidth} width={fieldWidth}
rowIndex={row.index} 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 ( return (
<Table style={{ width: `${totalWidth}px`, tableLayout: 'fixed' }}> <div className="min-w-max">
<TableHeader className="sticky top-0 z-10 bg-background"> <div className="relative">
<TableRow> {/* Custom Table Header - Always Visible */}
{table.getFlatHeaders().map((header) => ( <div
<TableHead className="sticky top-0 z-20 bg-muted border-b shadow-sm"
key={header.id} style={{ width: `${totalWidth}px` }}
style={{ >
width: `${header.getSize()}px`, <div className="flex">
minWidth: `${header.getSize()}px`, {table.getFlatHeaders().map((header, index) => {
maxWidth: `${header.getSize()}px`, const width = header.getSize();
position: 'sticky', return (
top: 0, <div
backgroundColor: 'inherit', key={header.id}
zIndex: 1 className="py-2 px-2 font-bold text-sm text-muted-foreground bg-muted flex items-center justify-center"
}} style={{
> width: `${width}px`,
{flexRender(header.column.columnDef.header, header.getContext())} minWidth: `${width}px`,
</TableHead> maxWidth: `${width}px`,
))} boxSizing: 'border-box',
</TableRow> height: '40px'
</TableHeader> }}
<TableBody> >
{table.getRowModel().rows.map((row) => ( {flexRender(header.column.columnDef.header, header.getContext())}
<TableRow </div>
key={row.id} );
className={cn( })}
"hover:bg-muted/50", </div>
row.getIsSelected() ? "bg-muted/50" : "", </div>
validationErrors.get(data.indexOf(row.original)) ? "bg-red-50/40" : ""
)} {/* Table Body */}
> <Table style={{ width: `${totalWidth}px`, tableLayout: 'fixed', borderCollapse: 'separate', borderSpacing: 0, marginTop: '-1px' }}>
{row.getVisibleCells().map((cell) => ( <TableBody>
<TableCell {table.getRowModel().rows.map((row) => (
key={cell.id} <TableRow
style={{ key={row.id}
width: `${cell.column.getSize()}px`, className={cn(
minWidth: `${cell.column.getSize()}px`, "hover:bg-muted/50",
maxWidth: `${cell.column.getSize()}px` 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())} {row.getVisibleCells().map((cell, cellIndex) => {
</TableCell> 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> </div>
</Table> </div>
); );
}; };

View File

@@ -30,7 +30,6 @@ interface MultiInputCellProps<T extends string> {
} }
// Add global CSS to ensure fixed width constraints - use !important to override other styles // 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 // Memoized option item to prevent unnecessary renders for large option lists
const OptionItem = React.memo(({ 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 we have a multi-select field with options, use command UI
if (field.fieldType.type === 'multi-select' && selectOptions.length > 0) { 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 ( return (
<div <Popover
ref={containerRef} open={open}
className="inline-block fixed-width-cell overflow-visible" onOpenChange={(isOpen) => {
style={fixedWidth} setOpen(isOpen);
data-width={cellWidth} handleOpenChange(isOpen);
}}
> >
<Popover open={open} onOpenChange={handleOpenChange}> <PopoverTrigger asChild>
<PopoverTrigger asChild> <Button
<Button variant="outline"
variant="outline" role="combobox"
role="combobox" aria-expanded={open}
aria-expanded={open} className={cn(
className={cn( "w-full justify-between font-normal",
"justify-between font-normal", "border",
!internalValue.length && "text-muted-foreground", !internalValue.length && "text-muted-foreground",
hasErrors && "border-red-500", hasErrors ? "border-destructive" : ""
"h-auto min-h-9 py-1" )}
)} onClick={(e) => {
onClick={() => setOpen(true)} e.preventDefault();
style={fixedWidth} e.stopPropagation();
> setOpen(!open);
<div className="flex items-center w-full justify-between"> if (!open && onStartEdit) onStartEdit();
<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}
> >
<Command shouldFilter={false} className="overflow-hidden"> <div className="flex items-center w-full justify-between">
<CommandInput <div className="flex items-center gap-2 overflow-hidden">
placeholder="Search options..." {internalValue.length === 0 ? (
className="h-9" <span className="text-muted-foreground truncate w-full">Select...</span>
value={searchQuery} ) : internalValue.length === 1 ? (
onValueChange={setSearchQuery} <span className="truncate w-full">{selectedValues[0].label}</span>
/> ) : (
<CommandList <>
className="overflow-hidden" <Badge variant="secondary" className="shrink-0 whitespace-nowrap">
ref={commandListRef} {internalValue.length} selected
onWheel={handleWheel} </Badge>
> <span className="truncate">
<CommandEmpty>No options found.</CommandEmpty> {selectedValues.map(v => v.label).join(', ')}
<CommandGroup> </span>
{sortedOptions.length > 0 ? ( </>
<VirtualizedOptions )}
options={sortedOptions} </div>
selectedValues={selectedValueSet} <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
onSelect={handleSelect} </div>
maxHeight={200} </Button>
/> </PopoverTrigger>
) : ( <PopoverContent
<div className="py-6 text-center text-sm">No options match your search</div> className="p-0 w-[var(--radix-popover-trigger-width)]"
)} align="start"
</CommandGroup> sideOffset={4}
</CommandList> >
</Command> <Command shouldFilter={false}>
</PopoverContent> <CommandInput
</Popover> placeholder="Search..."
</div> 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 // For standard multi-input without options, use text input
@@ -510,12 +467,6 @@ const MultiInputCell = <T extends string>({
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
// Create a key-value map for inline styles with fixed width - simplified // 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 // Use layout effect more efficiently - only for the button element
// since the container already uses inline styles // since the container already uses inline styles
@@ -539,6 +490,7 @@ const MultiInputCell = <T extends string>({
htmlButton.style.width = `${cellWidth}px`; htmlButton.style.width = `${cellWidth}px`;
htmlButton.style.minWidth = `${cellWidth}px`; htmlButton.style.minWidth = `${cellWidth}px`;
htmlButton.style.maxWidth = `${cellWidth}px`; htmlButton.style.maxWidth = `${cellWidth}px`;
htmlButton.style.boxSizing = 'border-box';
} }
} }
}, [cellWidth]); }, [cellWidth]);
@@ -547,7 +499,12 @@ const MultiInputCell = <T extends string>({
<div <div
ref={containerRef} ref={containerRef}
className="inline-block fixed-width-cell" className="inline-block fixed-width-cell"
style={fixedWidth} style={{
width: `${cellWidth}px`,
minWidth: `${cellWidth}px`,
maxWidth: `${cellWidth}px`,
boxSizing: 'border-box',
}}
data-width={cellWidth} data-width={cellWidth}
> >
{isMultiline ? ( {isMultiline ? (
@@ -562,7 +519,12 @@ const MultiInputCell = <T extends string>({
outlineClass, outlineClass,
hasErrors ? "border-destructive" : "" hasErrors ? "border-destructive" : ""
)} )}
style={fixedWidth} style={{
width: `${cellWidth}px`,
minWidth: `${cellWidth}px`,
maxWidth: `${cellWidth}px`,
boxSizing: 'border-box',
}}
/> />
) : ( ) : (
<div <div
@@ -571,10 +533,16 @@ const MultiInputCell = <T extends string>({
"cursor-text truncate", "cursor-text truncate",
outlineClass, outlineClass,
hasErrors ? "border-destructive" : "", hasErrors ? "border-destructive" : "",
"overflow-hidden items-center" "overflow-hidden items-center",
"w-full"
)} )}
onClick={handleFocus} onClick={handleFocus}
style={fixedWidth} style={{
width: `${cellWidth}px`,
minWidth: `${cellWidth}px`,
maxWidth: `${cellWidth}px`,
boxSizing: 'border-box',
}}
> >
{internalValue.length > 0 ? getDisplayValues().join(`, `) : ( {internalValue.length > 0 ? getDisplayValues().join(`, `) : (
<span className="text-muted-foreground truncate"> <span className="text-muted-foreground truncate">
@@ -589,7 +557,7 @@ const MultiInputCell = <T extends string>({
// Fallback to default behavior if no width is specified // Fallback to default behavior if no width is specified
return ( return (
<div className="w-full"> <div className="w-full overflow-hidden" style={{ boxSizing: 'border-box' }}>
{isMultiline ? ( {isMultiline ? (
<Textarea <Textarea
value={internalValue.join(separator)} value={internalValue.join(separator)}
@@ -598,10 +566,11 @@ const MultiInputCell = <T extends string>({
onBlur={handleBlur} onBlur={handleBlur}
placeholder={`Enter values separated by ${separator}`} placeholder={`Enter values separated by ${separator}`}
className={cn( className={cn(
"min-h-[80px] resize-none", "min-h-[80px] resize-none w-full",
outlineClass, outlineClass,
hasErrors ? "border-destructive" : "" hasErrors ? "border-destructive" : ""
)} )}
style={{ boxSizing: 'border-box' }}
/> />
) : ( ) : (
<div <div
@@ -613,9 +582,10 @@ const MultiInputCell = <T extends string>({
"overflow-hidden items-center" "overflow-hidden items-center"
)} )}
onClick={handleFocus} onClick={handleFocus}
style={{ boxSizing: 'border-box' }}
> >
{internalValue.length > 0 ? getDisplayValues().join(`, `) : ( {internalValue.length > 0 ? getDisplayValues().join(`, `) : (
<span className="text-muted-foreground"> <span className="text-muted-foreground truncate w-full">
{`Enter values separated by ${separator}`} {`Enter values separated by ${separator}`}
</span> </span>
)} )}