Skip to content

Commit 1286519

Browse files
authored
fix(resource): prevent permission-gated breadcrumb items from flashing on load (#4732)
* improvement(kb-connectors): align connector UI surfaces and strip redundant description suffix - Fix icon colors to use --text-icon token across connector cards and modal - Switch Reconnect/Update access buttons from active to primary variant - Lift search box surface from --surface-2 to --surface-3 so it's visible against modal bg - Replace raw <button> elements with emcn Button in base.tsx - Shrink Connected Sources modal from lg to md - Strip " to/into/from your knowledge base" from all 27 connector descriptions to prevent overflow * fix(resource): replace raw pagination button with emcn Button * fix(resource): prevent permission-gated breadcrumb items from flashing on load * fix(resource): self-remove keydown listener on Escape and include session loading in isLoading guard
1 parent a14d374 commit 1286519

34 files changed

Lines changed: 111 additions & 58 deletions

File tree

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

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,36 @@ export const ResourceTable = memo(function ResourceTable({
219219
direction: 'desc',
220220
})
221221

222+
const [contextMenuRowId, setContextMenuRowId] = useState<string | null>(null)
223+
224+
const wrappedOnRowContextMenu = useCallback(
225+
(e: React.MouseEvent, rowId: string) => {
226+
setContextMenuRowId(rowId)
227+
onRowContextMenu?.(e, rowId)
228+
},
229+
[onRowContextMenu]
230+
)
231+
232+
useEffect(() => {
233+
if (!contextMenuRowId) return
234+
const clear = () => setContextMenuRowId(null)
235+
const handleKeyDown = (e: KeyboardEvent) => {
236+
if (e.key === 'Escape') {
237+
document.removeEventListener('keydown', handleKeyDown)
238+
clear()
239+
}
240+
}
241+
const timeoutId = setTimeout(() => {
242+
document.addEventListener('pointerdown', clear, { once: true })
243+
document.addEventListener('keydown', handleKeyDown)
244+
}, 0)
245+
return () => {
246+
clearTimeout(timeoutId)
247+
document.removeEventListener('pointerdown', clear)
248+
document.removeEventListener('keydown', handleKeyDown)
249+
}
250+
}, [contextMenuRowId])
251+
222252
const handleSort = useCallback((column: string, direction: 'asc' | 'desc') => {
223253
setInternalSort({ column, direction })
224254
}, [])
@@ -343,7 +373,8 @@ export const ResourceTable = memo(function ResourceTable({
343373
rowDragDrop={rowDragDrop}
344374
onRowClick={onRowClick}
345375
onRowHover={onRowHover}
346-
onRowContextMenu={onRowContextMenu}
376+
onRowContextMenu={onRowContextMenu ? wrappedOnRowContextMenu : undefined}
377+
isContextMenuTarget={contextMenuRowId === row.id}
347378
hasCheckbox={hasCheckbox}
348379
/>
349380
))}
@@ -403,17 +434,18 @@ const Pagination = memo(function Pagination({
403434
}
404435
if (page < 1 || page > totalPages) return null
405436
return (
406-
<button
437+
<Button
407438
key={page}
408439
type='button'
440+
variant='ghost'
409441
onClick={() => onPageChange(page)}
410442
className={cn(
411-
'font-medium text-sm transition-colors hover-hover:text-[var(--text-body)]',
443+
'h-auto p-0 font-medium text-sm transition-colors hover-hover:bg-transparent hover-hover:text-[var(--text-body)]',
412444
page === currentPage ? 'text-[var(--text-body)]' : 'text-[var(--text-secondary)]'
413445
)}
414446
>
415447
{page}
416-
</button>
448+
</Button>
417449
)
418450
})}
419451
</div>
@@ -460,6 +492,7 @@ interface DataRowProps {
460492
onRowClick?: (rowId: string) => void
461493
onRowHover?: (rowId: string) => void
462494
onRowContextMenu?: (e: React.MouseEvent, rowId: string) => void
495+
isContextMenuTarget?: boolean
463496
hasCheckbox: boolean
464497
}
465498

@@ -472,6 +505,7 @@ const DataRow = memo(function DataRow({
472505
onRowClick,
473506
onRowHover,
474507
onRowContextMenu,
508+
isContextMenuTarget,
475509
hasCheckbox,
476510
}: DataRowProps) {
477511
const isSelected = selectable?.selectedIds.has(row.id) ?? false
@@ -554,7 +588,7 @@ const DataRow = memo(function DataRow({
554588
onRowClick && 'cursor-pointer',
555589
isDraggable && 'cursor-grab active:cursor-grabbing',
556590
isDropTarget && 'data-[drop-target=true]:outline-offset-[-1px]',
557-
(selectedRowId === row.id || isSelected) && 'bg-[var(--surface-3)]',
591+
(selectedRowId === row.id || isSelected || isContextMenuTarget) && 'bg-[var(--surface-3)]',
558592
isActiveDropTarget && 'bg-[var(--surface-4)] outline outline-1 outline-[var(--accent)]',
559593
(isDragging || (isAnyDragActive && isSelected && !isActiveDropTarget)) && 'opacity-50'
560594
)}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1585,10 +1585,11 @@ export function Files() {
15851585
}
15861586
: undefined,
15871587
dropdownItems:
1588-
isCurrentFolder && canEdit
1588+
isCurrentFolder && (canEdit || userPermissions.isLoading)
15891589
? [
15901590
{
15911591
label: 'Rename',
1592+
disabled: !canEdit,
15921593
onClick: () => breadcrumbRenameRef.current.startRename(folder.id, folder.name),
15931594
},
15941595
]
@@ -1605,6 +1606,7 @@ export function Files() {
16051606
router,
16061607
workspaceId,
16071608
canEdit,
1609+
userPermissions.isLoading,
16081610
breadcrumbRename.editingId,
16091611
breadcrumbRename.editValue,
16101612
])

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

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -814,24 +814,42 @@ export function KnowledgeBase({
814814
}
815815
: undefined,
816816
dropdownItems: [
817-
...(userPermissions.canEdit
817+
...(userPermissions.canEdit || userPermissions.isLoading
818818
? [
819819
{
820820
label: 'Rename',
821821
icon: Pencil,
822+
disabled: !userPermissions.canEdit,
822823
onClick: () => kbRename.startRename(id, knowledgeBaseName),
823824
},
824-
{ label: 'Tags', icon: Tag, onClick: () => setShowTagsModal(true) },
825-
{ label: 'Delete', icon: Trash, onClick: () => setShowDeleteDialog(true) },
825+
{
826+
label: 'Tags',
827+
icon: Tag,
828+
disabled: !userPermissions.canEdit,
829+
onClick: () => setShowTagsModal(true),
830+
},
831+
{
832+
label: 'Delete',
833+
icon: Trash,
834+
disabled: !userPermissions.canEdit,
835+
onClick: () => setShowDeleteDialog(true),
836+
},
826837
]
827838
: []),
828839
],
829840
},
830841
]
831842

832843
const headerActions: HeaderAction[] = [
833-
...(userPermissions.canEdit
834-
? [{ label: 'New connector', icon: Plus, onClick: () => setShowAddConnectorModal(true) }]
844+
...(userPermissions.canEdit || userPermissions.isLoading
845+
? [
846+
{
847+
label: 'New connector',
848+
icon: Plus,
849+
disabled: !userPermissions.canEdit,
850+
onClick: () => setShowAddConnectorModal(true),
851+
},
852+
]
835853
: []),
836854
]
837855

@@ -886,18 +904,18 @@ export function KnowledgeBase({
886904
/>
887905
</div>
888906
{enabledFilter.length > 0 && (
889-
<button
890-
type='button'
907+
<Button
908+
variant='ghost'
891909
onClick={() => {
892910
setEnabledFilter([])
893911
setCurrentPage(1)
894912
setSelectedDocuments(new Set())
895913
setIsSelectAllMode(false)
896914
}}
897-
className='flex h-[32px] w-full items-center justify-center rounded-md text-[var(--text-secondary)] text-caption transition-colors hover-hover:bg-[var(--surface-active)]'
915+
className='h-[32px] w-full text-[var(--text-secondary)] text-caption'
898916
>
899917
Clear status filter
900-
</button>
918+
</Button>
901919
)}
902920
<TagFilterSection
903921
tagDefinitions={tagDefinitions}
@@ -1322,7 +1340,7 @@ export function KnowledgeBase({
13221340
)}
13231341

13241342
<Modal open={showConnectorsModal} onOpenChange={setShowConnectorsModal}>
1325-
<ModalContent size='lg'>
1343+
<ModalContent size='md'>
13261344
<ModalHeader>Connected Sources</ModalHeader>
13271345
<ModalDescription className='sr-only'>
13281346
Manage connected data sources for this knowledge base
@@ -1535,13 +1553,13 @@ function TagFilterSection({ tagDefinitions, entries, onChange }: TagFilterSectio
15351553
>
15361554
<div className='flex items-center justify-between'>
15371555
<Label className='text-[var(--text-muted)] text-xs'>Tag</Label>
1538-
<button
1539-
type='button'
1556+
<Button
1557+
variant='ghost'
1558+
className='size-5 p-0 text-[var(--text-muted)] hover-hover:text-[var(--text-error)]'
15401559
onClick={() => removeFilter(entry.id)}
1541-
className='text-[var(--text-muted)] transition-colors hover-hover:text-[var(--text-error)]'
15421560
>
15431561
<X className='size-3' />
1544-
</button>
1562+
</Button>
15451563
</div>
15461564
<Combobox
15471565
options={tagOptions}

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ export function AddConnectorModal({
246246
<ModalBody className={step === 'select-type' ? 'pt-2 pb-3' : 'pb-3'}>
247247
{step === 'select-type' ? (
248248
<div className='flex min-h-0 flex-col gap-2.5'>
249-
<div className='flex h-8 items-center gap-2 rounded-md border border-[var(--border)] bg-[var(--surface-2)] px-2 transition-colors duration-100 hover-hover:border-[var(--border-1)] hover-hover:bg-[var(--surface-3)]'>
249+
<div className='flex h-8 items-center gap-2 rounded-md border border-[var(--border)] bg-[var(--surface-3)] px-2 transition-colors duration-100 hover-hover:border-[var(--border-1)] hover-hover:bg-[var(--surface-4)]'>
250250
<Search
251251
className='size-[14px] flex-shrink-0 text-[var(--text-icon)]'
252252
strokeWidth={2}
@@ -549,7 +549,7 @@ function ConnectorTypeCard({ config, onClick }: ConnectorTypeCardProps) {
549549
className='group flex min-h-10 w-full justify-start gap-2 rounded-md px-2 py-1.5 text-left transition-colors hover-hover:bg-[var(--surface-active)]'
550550
onClick={onClick}
551551
>
552-
<Icon className='size-[18px] flex-shrink-0' />
552+
<Icon className='size-[18px] flex-shrink-0 text-[var(--text-icon)]' />
553553
<div className='flex min-w-0 flex-1 flex-col gap-[1px]'>
554554
<span className='truncate font-medium text-[var(--text-body)] text-small'>
555555
{config.name}

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export function ConnectorSelectorField({
7272

7373
if (isLoading && isEnabled) {
7474
return (
75-
<div className='flex items-center gap-2 rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 py-1.5 font-medium text-[var(--text-muted)] text-sm'>
75+
<div className='flex items-center gap-2 rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 py-1.5 font-medium text-[var(--text-muted)] text-small'>
7676
<Loader className='size-3.5' animate />
7777
Loading…
7878
</div>

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connectors-section.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ function ConnectorCard({
372372
<div className='flex items-center justify-between gap-2 px-2 py-2'>
373373
<div className='flex min-w-0 items-center gap-2.5'>
374374
<div className='relative flex size-9 flex-shrink-0 items-center justify-center rounded-lg bg-[var(--surface-4)]'>
375-
{Icon && <Icon className='size-5 text-[var(--text-secondary)]' />}
375+
{Icon && <Icon className='size-5 text-[var(--text-icon)]' />}
376376
{connector.status === 'disabled' && (
377377
<AlertTriangle className='-right-0.5 -top-0.5 absolute size-3 text-[var(--caution)]' />
378378
)}
@@ -532,7 +532,7 @@ function ConnectorCard({
532532
</p>
533533
{canEdit && serviceId && providerId && (
534534
<Button
535-
variant='active'
535+
variant='primary'
536536
onClick={() => {
537537
if (connector.credentialId) {
538538
writeOAuthReturnContext({
@@ -566,7 +566,7 @@ function ConnectorCard({
566566
</div>
567567
{canEdit && (
568568
<Button
569-
variant='active'
569+
variant='primary'
570570
onClick={() => {
571571
if (connector.credentialId) {
572572
writeOAuthReturnContext({

apps/sim/connectors/airtable/airtable.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ function parseCursor(cursor?: string): string | undefined {
7575
export const airtableConnector: ConnectorConfig = {
7676
id: 'airtable',
7777
name: 'Airtable',
78-
description: 'Sync records from an Airtable table into your knowledge base',
78+
description: 'Sync records from an Airtable table',
7979
version: '1.0.0',
8080
icon: AirtableIcon,
8181

apps/sim/connectors/asana/asana.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ async function listWorkspaceProjects(
133133
export const asanaConnector: ConnectorConfig = {
134134
id: 'asana',
135135
name: 'Asana',
136-
description: 'Sync tasks from Asana into your knowledge base',
136+
description: 'Sync tasks from Asana',
137137
version: '1.0.0',
138138
icon: AsanaIcon,
139139

apps/sim/connectors/confluence/confluence.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ function cqlResultToStub(item: Record<string, unknown>, domain: string): Externa
146146
export const confluenceConnector: ConnectorConfig = {
147147
id: 'confluence',
148148
name: 'Confluence',
149-
description: 'Sync pages from a Confluence space into your knowledge base',
149+
description: 'Sync pages from a Confluence space',
150150
version: '1.1.0',
151151
icon: ConfluenceIcon,
152152

apps/sim/connectors/discord/discord.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ function formatMessages(messages: DiscordMessage[]): string {
137137
export const discordConnector: ConnectorConfig = {
138138
id: 'discord',
139139
name: 'Discord',
140-
description: 'Sync channel messages from Discord into your knowledge base',
140+
description: 'Sync channel messages from Discord',
141141
version: '1.0.0',
142142
icon: DiscordIcon,
143143

0 commit comments

Comments
 (0)