Skip to content

Commit 4f96fb5

Browse files
committed
improvement(workspace): fix resource table column proportions and toast stacking
1 parent b276672 commit 4f96fb5

11 files changed

Lines changed: 165 additions & 151 deletions

File tree

apps/sim/app/_styles/globals.css

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -878,22 +878,22 @@ input[type="search"]::-ms-clear {
878878
@keyframes toast-enter {
879879
from {
880880
opacity: 0;
881-
transform: translateY(8px) scale(0.97);
881+
transform: translateX(var(--stack-offset, 0px)) translateY(8px) scale(0.97);
882882
}
883883
to {
884884
opacity: 1;
885-
transform: translateY(0) scale(1);
885+
transform: translateX(var(--stack-offset, 0px)) translateY(0) scale(1);
886886
}
887887
}
888888

889889
@keyframes toast-exit {
890890
from {
891891
opacity: 1;
892-
transform: translateY(0) scale(1);
892+
transform: translateX(var(--stack-offset, 0px)) translateY(0) scale(1);
893893
}
894894
to {
895895
opacity: 0;
896-
transform: translateY(8px) scale(0.97);
896+
transform: translateX(var(--stack-offset, 0px)) translateY(8px) scale(0.97);
897897
}
898898
}
899899

apps/sim/app/workspace/[workspaceId]/components/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@ export type {
2525
RowDragDropConfig,
2626
SelectableConfig,
2727
} from './resource/resource'
28-
export { Resource, ResourceTable } from './resource/resource'
28+
export { EMPTY_CELL_PLACEHOLDER, Resource, ResourceTable } from './resource/resource'

apps/sim/app/workspace/[workspaceId]/components/resource/resource.tsx

Lines changed: 48 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ export interface ResourceColumn {
2323
id: string
2424
header: string
2525
widthMultiplier?: number
26-
/** Fixed pixel width. When set, the column is excluded from proportional sizing. */
27-
widthPx?: number
2826
}
2927

3028
export interface ResourceCell {
@@ -94,7 +92,7 @@ interface ResourceProps {
9492
overlay?: ReactNode
9593
}
9694

97-
const EMPTY_CELL_PLACEHOLDER = '- - -'
95+
export const EMPTY_CELL_PLACEHOLDER = ''
9896
const SKELETON_ROW_COUNT = 5
9997

10098
/**
@@ -214,20 +212,13 @@ export const ResourceTable = memo(function ResourceTable({
214212
emptyMessage,
215213
overlay,
216214
}: ResourceTableProps) {
217-
const headerRef = useRef<HTMLDivElement>(null)
218215
const loadMoreRef = useRef<HTMLDivElement>(null)
219216
const sortEnabled = defaultSort != null
220217
const [internalSort, setInternalSort] = useState<{ column: string; direction: 'asc' | 'desc' }>({
221218
column: defaultSort ?? '',
222219
direction: 'desc',
223220
})
224221

225-
const handleBodyScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => {
226-
if (headerRef.current) {
227-
headerRef.current.scrollLeft = e.currentTarget.scrollLeft
228-
}
229-
}, [])
230-
231222
const handleSort = useCallback((column: string, direction: 'asc' | 'desc') => {
232223
setInternalSort({ column, direction })
233224
}, [])
@@ -290,10 +281,10 @@ export const ResourceTable = memo(function ResourceTable({
290281

291282
return (
292283
<div className='relative flex min-h-0 flex-1 flex-col overflow-hidden'>
293-
<div ref={headerRef} className='overflow-hidden'>
284+
<div className='min-h-0 flex-1 overflow-auto'>
294285
<table className='w-full table-fixed text-small'>
295286
<ResourceColGroup columns={columns} hasCheckbox={hasCheckbox} />
296-
<thead className='shadow-[inset_0_-1px_0_var(--border)]'>
287+
<thead className='sticky top-0 z-10 bg-[var(--bg)] shadow-[inset_0_-1px_0_var(--border)]'>
297288
<tr>
298289
{hasCheckbox && (
299290
<th className='h-10 w-[52px] py-1.5 pr-0 pl-5 text-left align-middle'>
@@ -341,14 +332,6 @@ export const ResourceTable = memo(function ResourceTable({
341332
})}
342333
</tr>
343334
</thead>
344-
</table>
345-
</div>
346-
<div
347-
className='min-h-0 flex-1 overflow-auto [scrollbar-gutter:stable]'
348-
onScroll={handleBodyScroll}
349-
>
350-
<table className='w-full table-fixed text-small'>
351-
<ResourceColGroup columns={columns} hasCheckbox={hasCheckbox} />
352335
<tbody>
353336
{displayRows.map((row) => (
354337
<DataRow
@@ -644,43 +627,22 @@ interface ResourceColGroupProps {
644627
hasCheckbox?: boolean
645628
}
646629

647-
const CHECKBOX_COLUMN_WIDTH_PX = 52
648-
const CHECKBOX_COLUMN_WIDTH = `${CHECKBOX_COLUMN_WIDTH_PX}px`
630+
const CHECKBOX_COLUMN_WIDTH = '52px'
649631

650632
const ResourceColGroup = memo(function ResourceColGroup({
651633
columns,
652634
hasCheckbox,
653635
}: ResourceColGroupProps) {
654-
const fixedPxTotal = columns.reduce((sum, col) => sum + (col.widthPx ?? 0), 0)
655-
const flexibleWeights = columns.map((col, colIdx) =>
656-
col.widthPx ? 0 : (colIdx === 0 ? 2.5 : 1.0) * (col.widthMultiplier ?? 1)
636+
const weights = columns.map(
637+
(col, colIdx) => (colIdx === 0 ? 2.5 : 1.0) * (col.widthMultiplier ?? 1)
657638
)
658-
const flexibleTotal = flexibleWeights.reduce((s, w) => s + w, 0)
659-
const reservedPx = fixedPxTotal + (hasCheckbox ? CHECKBOX_COLUMN_WIDTH_PX : 0)
660-
639+
const total = weights.reduce((s, w) => s + w, 0)
661640
return (
662641
<colgroup>
663642
{hasCheckbox && <col style={{ width: CHECKBOX_COLUMN_WIDTH }} />}
664-
{columns.map((col, colIdx) => {
665-
if (col.widthPx) {
666-
return <col key={col.id} style={{ width: `${col.widthPx}px` }} />
667-
}
668-
const columnRatio = flexibleTotal > 0 ? flexibleWeights[colIdx] / flexibleTotal : 0
669-
const columnPercent = columnRatio * 100
670-
const reservedOffset = reservedPx * columnRatio
671-
672-
return (
673-
<col
674-
key={col.id}
675-
style={{
676-
width:
677-
reservedOffset > 0
678-
? `calc(${columnPercent}% - ${reservedOffset}px)`
679-
: `${columnPercent}%`,
680-
}}
681-
/>
682-
)
683-
})}
643+
{columns.map((col, colIdx) => (
644+
<col key={col.id} style={{ width: `${((weights[colIdx] / total) * 100).toFixed(3)}%` }} />
645+
))}
684646
</colgroup>
685647
)
686648
})
@@ -697,55 +659,48 @@ const DataTableSkeleton = memo(function DataTableSkeleton({
697659
hasCheckbox,
698660
}: DataTableSkeletonProps) {
699661
return (
700-
<>
701-
<div className='overflow-hidden'>
702-
<table className='w-full table-fixed text-small'>
703-
<ResourceColGroup columns={columns} hasCheckbox={hasCheckbox} />
704-
<thead className='shadow-[inset_0_-1px_0_var(--border)]'>
705-
<tr>
662+
<div className='min-h-0 flex-1 overflow-auto'>
663+
<table className='w-full table-fixed text-small'>
664+
<ResourceColGroup columns={columns} hasCheckbox={hasCheckbox} />
665+
<thead className='sticky top-0 z-10 bg-[var(--bg)] shadow-[inset_0_-1px_0_var(--border)]'>
666+
<tr>
667+
{hasCheckbox && (
668+
<th className='h-10 w-[52px] py-2.5 pr-0 pl-5 text-left align-middle'>
669+
<Skeleton className='size-[14px] rounded-xs' />
670+
</th>
671+
)}
672+
{columns.map((col) => (
673+
<th
674+
key={col.id}
675+
className='h-10 px-6 py-2.5 text-left align-middle font-base text-[var(--text-muted)]'
676+
>
677+
<div className='flex min-h-[20px] items-center'>
678+
<Skeleton className='h-[12px] w-[56px]' />
679+
</div>
680+
</th>
681+
))}
682+
</tr>
683+
</thead>
684+
<tbody>
685+
{Array.from({ length: rowCount }, (_, i) => (
686+
<tr key={i}>
706687
{hasCheckbox && (
707-
<th className='h-10 w-[52px] py-2.5 pr-0 pl-5 text-left align-middle'>
688+
<td className='w-[52px] py-2.5 pr-0 pl-5 align-middle'>
708689
<Skeleton className='size-[14px] rounded-xs' />
709-
</th>
690+
</td>
710691
)}
711-
{columns.map((col) => (
712-
<th
713-
key={col.id}
714-
className='h-10 px-6 py-2.5 text-left align-middle font-base text-[var(--text-muted)]'
715-
>
716-
<div className='flex min-h-[20px] items-center'>
717-
<Skeleton className='h-[12px] w-[56px]' />
718-
</div>
719-
</th>
692+
{columns.map((col, colIdx) => (
693+
<td key={col.id} className='px-6 py-2.5 align-middle'>
694+
<span className='flex min-h-[21px] items-center gap-3'>
695+
{colIdx === 0 && <Skeleton className='size-[14px] rounded-xs' />}
696+
<Skeleton className='h-[14px] w-[128px]' />
697+
</span>
698+
</td>
720699
))}
721700
</tr>
722-
</thead>
723-
</table>
724-
</div>
725-
<div className='min-h-0 flex-1 overflow-auto'>
726-
<table className='w-full table-fixed text-small'>
727-
<ResourceColGroup columns={columns} hasCheckbox={hasCheckbox} />
728-
<tbody>
729-
{Array.from({ length: rowCount }, (_, i) => (
730-
<tr key={i}>
731-
{hasCheckbox && (
732-
<td className='w-[52px] py-2.5 pr-0 pl-5 align-middle'>
733-
<Skeleton className='size-[14px] rounded-xs' />
734-
</td>
735-
)}
736-
{columns.map((col, colIdx) => (
737-
<td key={col.id} className='px-6 py-2.5 align-middle'>
738-
<span className='flex min-h-[21px] items-center gap-3'>
739-
{colIdx === 0 && <Skeleton className='size-[14px] rounded-xs' />}
740-
<Skeleton className='h-[14px] w-[128px]' />
741-
</span>
742-
</td>
743-
))}
744-
</tr>
745-
))}
746-
</tbody>
747-
</table>
748-
</div>
749-
</>
701+
))}
702+
</tbody>
703+
</table>
704+
</div>
750705
)
751706
})

apps/sim/app/workspace/[workspaceId]/files/files.tsx

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import type {
5656
SortConfig,
5757
} from '@/app/workspace/[workspaceId]/components'
5858
import {
59+
EMPTY_CELL_PLACEHOLDER,
5960
InlineRenameInput,
6061
ownerCell,
6162
Resource,
@@ -112,12 +113,12 @@ const SUPPORTED_EXTENSIONS = [
112113
const ACCEPT_ATTR = SUPPORTED_EXTENSIONS.map((ext) => `.${ext}`).join(',')
113114

114115
const COLUMNS: ResourceColumn[] = [
115-
{ id: 'name', header: 'Name' },
116-
{ id: 'size', header: 'Size', widthPx: 110 },
117-
{ id: 'type', header: 'Type', widthPx: 120 },
118-
{ id: 'created', header: 'Created', widthPx: 150 },
119-
{ id: 'owner', header: 'Owner', widthPx: 160 },
120-
{ id: 'updated', header: 'Last Updated', widthPx: 150 },
116+
{ id: 'name', header: 'Name', widthMultiplier: 1.15 },
117+
{ id: 'size', header: 'Size', widthMultiplier: 0.7 },
118+
{ id: 'type', header: 'Type', widthMultiplier: 0.85 },
119+
{ id: 'created', header: 'Created' },
120+
{ id: 'owner', header: 'Owner' },
121+
{ id: 'updated', header: 'Last Updated' },
121122
]
122123

123124
const MIME_TYPE_LABELS: Record<string, string> = {
@@ -301,6 +302,26 @@ export function Files() {
301302

302303
const folderById = useMemo(() => new Map(folders.map((folder) => [folder.id, folder])), [folders])
303304
const currentFolder = currentFolderId ? (folderById.get(currentFolderId) ?? null) : null
305+
306+
const folderSizeMap = useMemo(() => {
307+
const directSize = new Map<string, number>()
308+
for (const file of files) {
309+
if (file.folderId) {
310+
directSize.set(file.folderId, (directSize.get(file.folderId) ?? 0) + file.size)
311+
}
312+
}
313+
const totalSize = new Map<string, number>()
314+
const getTotal = (folderId: string): number => {
315+
if (totalSize.has(folderId)) return totalSize.get(folderId)!
316+
const children = folders.filter((f) => f.parentId === folderId)
317+
const size =
318+
(directSize.get(folderId) ?? 0) + children.reduce((s, c) => s + getTotal(c.id), 0)
319+
totalSize.set(folderId, size)
320+
return size
321+
}
322+
for (const folder of folders) getTotal(folder.id)
323+
return totalSize
324+
}, [files, folders])
304325
const currentFolderPath = currentFolder?.path ?? null
305326

306327
const visibleFolders = useMemo(() => {
@@ -406,7 +427,12 @@ export function Files() {
406427
icon: <Folder className='size-[14px]' />,
407428
label: folder.name,
408429
},
409-
size: { label: 'Folder' },
430+
size: {
431+
label:
432+
(folderSizeMap.get(folder.id) ?? 0) > 0
433+
? formatFileSize(folderSizeMap.get(folder.id)!, { includeBytes: true })
434+
: EMPTY_CELL_PLACEHOLDER,
435+
},
410436
type: {
411437
icon: <Folder className='size-[14px]' />,
412438
label: 'Folder',
@@ -458,7 +484,7 @@ export function Files() {
458484
})
459485

460486
return [...folderRows, ...fileRows]
461-
}, [visibleFolders, filteredFiles, members])
487+
}, [visibleFolders, filteredFiles, members, folderSizeMap])
462488

463489
const rows: ResourceRow[] = useMemo(() => {
464490
if (!listRename.editingId) return baseRows

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ import type {
3030
SelectableConfig,
3131
SortConfig,
3232
} from '@/app/workspace/[workspaceId]/components'
33-
import { Resource, ResourceHeader } from '@/app/workspace/[workspaceId]/components'
33+
import {
34+
EMPTY_CELL_PLACEHOLDER,
35+
Resource,
36+
ResourceHeader,
37+
} from '@/app/workspace/[workspaceId]/components'
3438
import {
3539
ChunkContextMenu,
3640
ChunkEditor,
@@ -127,9 +131,9 @@ function truncateContent(content: string, maxLength = 150, searchQuery = ''): st
127131

128132
const CHUNK_COLUMNS: ResourceColumn[] = [
129133
{ id: 'content', header: 'Content' },
130-
{ id: 'index', header: 'Index', widthPx: 100 },
131-
{ id: 'tokens', header: 'Tokens', widthPx: 100 },
132-
{ id: 'status', header: 'Status', widthPx: 120 },
134+
{ id: 'index', header: 'Index', widthMultiplier: 0.6 },
135+
{ id: 'tokens', header: 'Tokens', widthMultiplier: 0.6 },
136+
{ id: 'status', header: 'Status', widthMultiplier: 0.75 },
133137
]
134138

135139
export function Document({
@@ -874,9 +878,9 @@ export function Document({
874878
</div>
875879
),
876880
},
877-
index: { label: '—' },
878-
tokens: { label: '—' },
879-
status: { label: '—' },
881+
index: { label: EMPTY_CELL_PLACEHOLDER },
882+
tokens: { label: EMPTY_CELL_PLACEHOLDER },
883+
status: { label: EMPTY_CELL_PLACEHOLDER },
880884
},
881885
},
882886
]

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,13 @@ const logger = createLogger('KnowledgeBase')
8585
const DOCUMENTS_PER_PAGE = 50
8686

8787
const DOCUMENT_COLUMNS: ResourceColumn[] = [
88-
{ id: 'name', header: 'Name' },
89-
{ id: 'size', header: 'Size', widthPx: 90 },
90-
{ id: 'tokens', header: 'Tokens', widthPx: 90 },
91-
{ id: 'chunks', header: 'Chunks', widthPx: 90 },
92-
{ id: 'uploaded', header: 'Uploaded', widthPx: 140 },
93-
{ id: 'status', header: 'Status', widthPx: 110 },
94-
{ id: 'tags', header: 'Tags', widthPx: 160 },
88+
{ id: 'name', header: 'Name', widthMultiplier: 0.8 },
89+
{ id: 'size', header: 'Size', widthMultiplier: 0.75 },
90+
{ id: 'tokens', header: 'Tokens', widthMultiplier: 0.75 },
91+
{ id: 'chunks', header: 'Chunks', widthMultiplier: 0.75 },
92+
{ id: 'uploaded', header: 'Uploaded' },
93+
{ id: 'status', header: 'Status', widthMultiplier: 0.75 },
94+
{ id: 'tags', header: 'Tags' },
9595
]
9696

9797
interface KnowledgeBaseProps {

0 commit comments

Comments
 (0)