Skip to content

Commit 0990a17

Browse files
authored
fix(settings): chip-consistency + shared credential-style resource row (#5308)
- chip: move default/filled hover into active-keyed compound variants so raw chipVariants({...}) renders identically to cn(chipVariants({...})); fixes sidebar/settings-sidebar active-hover divergence (no change to cn-wrapped consumers) - integrations: 'Explore in chat' uses active chip (darkens on hover) instead of floating text - data-retention: fold the retention-policies helper into the page description; drop the redundant wrapper - settings: extract shared SettingsResourceRow (rounded icon tile + title/desc + trailing, icons normalized to 20px); migrate recently-deleted, byok, and credential-sets onto it; recently-deleted actions are now Chips
1 parent 5055b31 commit 0990a17

9 files changed

Lines changed: 179 additions & 160 deletions

File tree

apps/sim/app/workspace/[workspaceId]/integrations/components/showcase-with-explore/showcase-with-explore.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export function ShowcaseWithExplore({ prompt }: ShowcaseWithExploreProps) {
3030
<div className='relative'>
3131
<IntegrationsShowcase />
3232
<Chip
33+
active
3334
rightIcon={ArrowRight}
3435
onClick={() => {
3536
storeCuratedPrompt(prompt)

apps/sim/app/workspace/[workspaceId]/settings/components/byok/byok-key-manager.tsx

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
import { BYOKProviderKeysModal } from '@/app/workspace/[workspaceId]/settings/components/byok/byok-provider-keys-modal'
2424
import { BYOKKeySkeleton } from '@/app/workspace/[workspaceId]/settings/components/byok/byok-skeleton'
2525
import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state'
26+
import { SettingsResourceRow } from '@/app/workspace/[workspaceId]/settings/components/settings-resource-row'
2627
import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section'
2728

2829
const logger = createLogger('BYOKKeyManager')
@@ -278,21 +279,13 @@ export function BYOKKeyManager(props: BYOKKeyManagerProps) {
278279
const Icon = provider.icon
279280

280281
return (
281-
<div key={provider.id} className='flex items-center justify-between gap-2.5'>
282-
<div className='flex min-w-0 items-center gap-2.5'>
283-
<div className='flex size-9 flex-shrink-0 items-center justify-center overflow-hidden rounded-xl border border-[var(--border-1)] bg-[var(--bg)]'>
284-
<Icon className='size-5' />
285-
</div>
286-
<div className='flex min-w-0 flex-col justify-center gap-[1px]'>
287-
<span className='truncate text-[14px] text-[var(--text-body)]'>{provider.name}</span>
288-
<span className='truncate text-[12px] text-[var(--text-muted)]'>
289-
{provider.description}
290-
</span>
291-
</div>
292-
</div>
293-
294-
{renderActions(provider)}
295-
</div>
282+
<SettingsResourceRow
283+
key={provider.id}
284+
icon={<Icon />}
285+
title={provider.name}
286+
description={provider.description}
287+
trailing={renderActions(provider)}
288+
/>
296289
)
297290
}
298291

apps/sim/app/workspace/[workspaceId]/settings/components/credential-sets/credential-sets.tsx

Lines changed: 40 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { getUserRole } from '@/lib/workspaces/organization'
3434
import { RowActionsMenu } from '@/app/workspace/[workspaceId]/settings/components/row-actions-menu'
3535
import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state'
3636
import { SettingsPanel } from '@/app/workspace/[workspaceId]/settings/components/settings-panel'
37+
import { SettingsResourceRow } from '@/app/workspace/[workspaceId]/settings/components/settings-resource-row'
3738
import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section'
3839
import {
3940
type CredentialSet,
@@ -632,68 +633,48 @@ export function CredentialSets() {
632633
<div className='flex flex-col gap-4.5'>
633634
{filteredInvitations.length > 0 && (
634635
<SettingsSection label='Pending Invitations'>
635-
<div className='flex flex-col gap-3'>
636+
<div className='flex flex-col gap-2'>
636637
{filteredInvitations.map((invitation) => (
637-
<div
638+
<SettingsResourceRow
638639
key={invitation.invitationId}
639-
className='flex items-center justify-between rounded-lg p-2 transition-colors hover-hover:bg-[var(--surface-active)]'
640-
>
641-
<div className='flex items-center gap-2.5'>
642-
<div className='flex size-9 flex-shrink-0 items-center justify-center rounded-xl border border-[var(--border-1)] bg-[var(--bg)]'>
643-
{getProviderIcon(invitation.providerId)}
644-
</div>
645-
<div className='flex flex-col'>
646-
<span className='text-[14px] text-[var(--text-body)]'>
647-
{invitation.credentialSetName}
648-
</span>
649-
<span className='text-[12px] text-[var(--text-muted)]'>
650-
{invitation.organizationName}
651-
</span>
652-
</div>
653-
</div>
654-
<Chip
655-
variant='primary'
656-
onClick={() => handleAcceptInvitation(invitation.token)}
657-
disabled={acceptInvitation.isPending}
658-
>
659-
{acceptInvitation.isPending ? 'Accepting...' : 'Accept'}
660-
</Chip>
661-
</div>
640+
icon={getProviderIcon(invitation.providerId)}
641+
title={invitation.credentialSetName}
642+
description={invitation.organizationName}
643+
trailing={
644+
<Chip
645+
variant='primary'
646+
onClick={() => handleAcceptInvitation(invitation.token)}
647+
disabled={acceptInvitation.isPending}
648+
>
649+
{acceptInvitation.isPending ? 'Accepting...' : 'Accept'}
650+
</Chip>
651+
}
652+
/>
662653
))}
663654
</div>
664655
</SettingsSection>
665656
)}
666657

667658
{filteredMemberships.length > 0 && (
668659
<SettingsSection label='My Memberships'>
669-
<div className='flex flex-col gap-3'>
660+
<div className='flex flex-col gap-2'>
670661
{filteredMemberships.map((membership) => (
671-
<div
662+
<SettingsResourceRow
672663
key={membership.membershipId}
673-
className='flex items-center justify-between rounded-lg p-2 transition-colors hover-hover:bg-[var(--surface-active)]'
674-
>
675-
<div className='flex items-center gap-2.5'>
676-
<div className='flex size-9 flex-shrink-0 items-center justify-center rounded-xl border border-[var(--border-1)] bg-[var(--bg)]'>
677-
{getProviderIcon(membership.providerId)}
678-
</div>
679-
<div className='flex flex-col'>
680-
<span className='text-[14px] text-[var(--text-body)]'>
681-
{membership.credentialSetName}
682-
</span>
683-
<span className='text-[12px] text-[var(--text-muted)]'>
684-
{membership.organizationName}
685-
</span>
686-
</div>
687-
</div>
688-
<Chip
689-
onClick={() =>
690-
handleLeave(membership.credentialSetId, membership.credentialSetName)
691-
}
692-
disabled={leaveCredentialSet.isPending}
693-
>
694-
Leave
695-
</Chip>
696-
</div>
664+
icon={getProviderIcon(membership.providerId)}
665+
title={membership.credentialSetName}
666+
description={membership.organizationName}
667+
trailing={
668+
<Chip
669+
onClick={() =>
670+
handleLeave(membership.credentialSetId, membership.credentialSetName)
671+
}
672+
disabled={leaveCredentialSet.isPending}
673+
>
674+
Leave
675+
</Chip>
676+
}
677+
/>
697678
))}
698679
</div>
699680
</SettingsSection>
@@ -709,26 +690,14 @@ export function CredentialSets() {
709690
No polling groups created yet
710691
</div>
711692
) : (
712-
<div className='flex flex-col gap-3'>
693+
<div className='flex flex-col gap-2'>
713694
{filteredOwnedSets.map((set) => (
714-
<div
695+
<SettingsResourceRow
715696
key={set.id}
716-
className='flex items-center justify-between rounded-lg p-2 transition-colors hover-hover:bg-[var(--surface-active)]'
717-
>
718-
<div className='flex items-center gap-2.5'>
719-
<div className='flex size-9 flex-shrink-0 items-center justify-center rounded-xl border border-[var(--border-1)] bg-[var(--bg)]'>
720-
{getProviderIcon(set.providerId)}
721-
</div>
722-
<div className='flex flex-col'>
723-
<span className='text-[14px] text-[var(--text-body)]'>
724-
{set.name}
725-
</span>
726-
<span className='text-[12px] text-[var(--text-muted)]'>
727-
{set.memberCount} member{set.memberCount !== 1 ? 's' : ''}
728-
</span>
729-
</div>
730-
</div>
731-
<div className='flex items-center gap-1'>
697+
icon={getProviderIcon(set.providerId)}
698+
title={set.name}
699+
description={`${set.memberCount} member${set.memberCount !== 1 ? 's' : ''}`}
700+
trailing={
732701
<RowActionsMenu
733702
label='Group actions'
734703
actions={[
@@ -741,8 +710,8 @@ export function CredentialSets() {
741710
},
742711
]}
743712
/>
744-
</div>
745-
</div>
713+
}
714+
/>
746715
))}
747716
</div>
748717
)}

apps/sim/app/workspace/[workspaceId]/settings/components/recently-deleted/recently-deleted.tsx

Lines changed: 33 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client'
22

33
import { useCallback, useMemo, useState } from 'react'
4-
import { Button, ChipInput, ChipModalTabs } from '@sim/emcn'
4+
import { Chip, ChipInput, ChipModalTabs } from '@sim/emcn'
55
import { Folder, Search, Workflow } from '@sim/emcn/icons'
66
import { toError } from '@sim/utils/errors'
77
import { formatDate } from '@sim/utils/formatting'
@@ -21,6 +21,7 @@ import {
2121
} from '@/app/workspace/[workspaceId]/settings/components/recently-deleted/search-params'
2222
import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state'
2323
import { SettingsPanel } from '@/app/workspace/[workspaceId]/settings/components/settings-panel'
24+
import { SettingsResourceRow } from '@/app/workspace/[workspaceId]/settings/components/settings-resource-row'
2425
import { useFolders, useRestoreFolder } from '@/hooks/queries/folders'
2526
import { useKnowledgeBasesQuery, useRestoreKnowledgeBase } from '@/hooks/queries/kb/knowledge'
2627
import { useRestoreTable, useTablesList } from '@/hooks/queries/tables'
@@ -82,7 +83,7 @@ const SORT_OPTIONS: ColumnOption[] = [
8283
{ id: 'type', label: 'Type' },
8384
]
8485

85-
const ICON_CLASS = 'size-[14px]'
86+
const ICON_CLASS = 'size-5'
8687

8788
const RESOURCE_TYPE_TO_MOTHERSHIP: Partial<
8889
Record<Exclude<ResourceType, 'all'>, MothershipResourceType>
@@ -464,45 +465,40 @@ export function RecentlyDeleted() {
464465
const isRestored = restoredItems.has(resource.id)
465466

466467
return (
467-
<div
468+
<SettingsResourceRow
468469
key={resource.id}
469-
className='flex items-center gap-2.5 rounded-lg p-2 transition-colors hover-hover:bg-[var(--surface-active)]'
470-
>
471-
<ResourceIcon resource={resource} />
472-
473-
<div className='flex min-w-0 flex-1 flex-col'>
474-
<span className='truncate font-medium text-[var(--text-primary)] text-small'>
475-
{resource.name}
476-
</span>
477-
<span className='text-[var(--text-muted)] text-small'>
470+
icon={<ResourceIcon resource={resource} />}
471+
title={resource.name}
472+
description={
473+
<>
478474
{TYPE_LABEL[resource.type]}
479475
{' \u00b7 '}
480476
Deleted {formatDate(resource.deletedAt)}
481-
</span>
482-
</div>
483-
484-
{isRestoring ? (
485-
<Button variant='primary' size='sm' disabled className='shrink-0'>
486-
Restoring...
487-
</Button>
488-
) : isRestored ? (
489-
<div className='flex shrink-0 items-center gap-2'>
490-
<span className='text-[var(--text-muted)] text-small'>Restored</span>
491-
<Button variant='primary' size='sm' onClick={() => handleView(resource)}>
492-
View
493-
</Button>
494-
</div>
495-
) : (
496-
<Button
497-
variant='primary'
498-
size='sm'
499-
onClick={() => void handleRestore(resource)}
500-
className='shrink-0'
501-
>
502-
Restore
503-
</Button>
504-
)}
505-
</div>
477+
</>
478+
}
479+
trailing={
480+
isRestoring ? (
481+
<Chip variant='primary' disabled className='shrink-0'>
482+
Restoring...
483+
</Chip>
484+
) : isRestored ? (
485+
<div className='flex shrink-0 items-center gap-2'>
486+
<span className='text-[var(--text-muted)] text-small'>Restored</span>
487+
<Chip variant='primary' onClick={() => handleView(resource)}>
488+
View
489+
</Chip>
490+
</div>
491+
) : (
492+
<Chip
493+
variant='primary'
494+
onClick={() => void handleRestore(resource)}
495+
className='shrink-0'
496+
>
497+
Restore
498+
</Chip>
499+
)
500+
}
501+
/>
506502
)
507503
})}
508504
</div>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { SettingsResourceRow } from './settings-resource-row'
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { ReactNode } from 'react'
2+
3+
/**
4+
* The canonical settings "resource row": a rounded-bordered icon tile, a
5+
* title + muted description text block, and an optional trailing slot
6+
* (action chips, a {@link RowActionsMenu}, a status label, etc.).
7+
*
8+
* Single source of truth for the credential-style row shared by the BYOK key
9+
* manager, credential sets, and recently-deleted lists — never re-derive the
10+
* tile/text chrome per consumer. The tile force-sizes any `<svg>`/`<img>` it
11+
* contains to 20px, so callers pass their raw icon node without pre-sizing it.
12+
*/
13+
interface SettingsResourceRowProps {
14+
/** Icon node centered in the tile; any `<svg>`/`<img>` is normalized to 20px. */
15+
icon: ReactNode
16+
/** Primary line — truncates. */
17+
title: ReactNode
18+
/** Secondary muted line — truncates. */
19+
description?: ReactNode
20+
/** Trailing element pinned to the row's end (chips, actions menu, status). */
21+
trailing?: ReactNode
22+
}
23+
24+
const TILE_CLASS =
25+
'flex size-9 flex-shrink-0 items-center justify-center overflow-hidden rounded-xl border border-[var(--border-1)] bg-[var(--bg)] [&_img]:size-5 [&_svg]:size-5'
26+
27+
export function SettingsResourceRow({
28+
icon,
29+
title,
30+
description,
31+
trailing,
32+
}: SettingsResourceRowProps) {
33+
return (
34+
<div className='flex items-center justify-between gap-2.5'>
35+
<div className='flex min-w-0 items-center gap-2.5'>
36+
<div className={TILE_CLASS}>{icon}</div>
37+
<div className='flex min-w-0 flex-col justify-center gap-[1px]'>
38+
<span className='truncate text-[14px] text-[var(--text-body)]'>{title}</span>
39+
{description != null && (
40+
<span className='truncate text-[12px] text-[var(--text-muted)]'>{description}</span>
41+
)}
42+
</div>
43+
</div>
44+
{trailing}
45+
</div>
46+
)
47+
}

apps/sim/app/workspace/[workspaceId]/settings/navigation.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,8 @@ export const allNavigationItems: NavigationItem[] = [
248248
{
249249
id: 'data-retention',
250250
label: 'Data retention',
251-
description: 'Control data retention windows and PII redaction.',
251+
description:
252+
'Control data retention windows and PII redaction. Workspaces without an override inherit the organization defaults.',
252253
icon: Database,
253254
section: 'enterprise',
254255
requiresHosted: true,

0 commit comments

Comments
 (0)