Skip to content

Commit c3189a7

Browse files
committed
refactor(settings): chip design-system consistency pass across all tabs
Extract a shared chip-field shell (CHIP_FIELD_SHELL/CHIP_FIELD_INPUT) mirroring Input variant='chip' and route secrets, credential detail, and integrations credential detail through it (30px height, font-medium, focus ring). Add a Discard action to the secrets header when dirty. Group BYOK providers into Models/Search & web/Enrichment sections and align its tiles to the integrations tile. Normalize list-row typography to text-[14px]/text-[12px] and icon tiles to rounded-xl + border across api-keys, copilot, custom-tools, mcp, workflow-mcp-servers, credential-sets, and access-control. Tone the secrets Details chip and per-row affordances to ghost. Fix token correctness: raw tailwind colors to design tokens (data-drains), missing chip variant on the Snowflake role input, ColorInput chip-field reuse (whitelabeling), error token and icon sizes (workflow-mcp-servers), border token (mcp), no-results sizing (secrets), row chrome (recently-deleted), hover token and Button to Chip (access-control), and deduped textarea chrome (sso).
1 parent 52739a3 commit c3189a7

20 files changed

Lines changed: 267 additions & 175 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Shared chip-field chrome for the credential and secret detail surfaces.
3+
*
4+
* These mirror `Input variant='chip'` exactly (30px tall, `rounded-lg`,
5+
* `border-1`, `surface-5`/`surface-4`, `font-medium` body text, and a
6+
* `border-focus` ring on focus) but as a wrapper + inner-input pair, so a field
7+
* can host a borderless input alongside a trailing slot (a copy button, a
8+
* reveal toggle). Using one definition keeps every chip field — list rows,
9+
* copyable IDs, secret values, display-name/description editors — pixel-identical
10+
* to the canonical chip input instead of each re-deriving the tokens.
11+
*/
12+
13+
/** Pill wrapper. Override height/alignment (e.g. a textarea) via `cn`. */
14+
export const CHIP_FIELD_SHELL =
15+
'flex h-[30px] items-center gap-2 rounded-lg border border-[var(--border-1)] bg-[var(--surface-5)] px-2 transition-colors focus-within:border-[var(--border-focus)] dark:bg-[var(--surface-4)]'
16+
17+
/** Borderless input/textarea hosted inside {@link CHIP_FIELD_SHELL}. */
18+
export const CHIP_FIELD_INPUT =
19+
'h-full w-full bg-transparent font-medium text-[var(--text-body)] text-sm outline-none placeholder:text-[var(--text-muted)] focus:outline-none'

apps/sim/app/workspace/[workspaceId]/components/credential-detail/components/copyable-value-field.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
import { useState } from 'react'
44
import { Button, Tooltip } from '@/components/emcn'
55
import { Check, Duplicate } from '@/components/emcn/icons'
6+
import { cn } from '@/lib/core/utils/cn'
7+
import {
8+
CHIP_FIELD_INPUT,
9+
CHIP_FIELD_SHELL,
10+
} from '@/app/workspace/[workspaceId]/components/credential-detail/components/chip-field'
611

712
interface CopyableValueFieldProps {
813
value: string
@@ -19,13 +24,8 @@ export function CopyableValueField({ value, copyLabel, id }: CopyableValueFieldP
1924
const [copied, setCopied] = useState(false)
2025

2126
return (
22-
<div className='flex h-[30px] items-center gap-2 rounded-lg border border-[var(--border-1)] bg-[var(--surface-5)] px-2 dark:bg-[var(--surface-4)]'>
23-
<input
24-
id={id}
25-
readOnly
26-
value={value}
27-
className='h-full w-full cursor-default bg-transparent text-[var(--text-body)] text-sm outline-none placeholder:text-[var(--text-muted)] focus:outline-none'
28-
/>
27+
<div className={CHIP_FIELD_SHELL}>
28+
<input id={id} readOnly value={value} className={cn(CHIP_FIELD_INPUT, 'cursor-default')} />
2929
<Tooltip.Root>
3030
<Tooltip.Trigger asChild>
3131
<Button

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export { AddPeopleModal } from './components/add-people-modal'
2+
export { CHIP_FIELD_INPUT, CHIP_FIELD_SHELL } from './components/chip-field'
23
export { CopyableValueField } from './components/copyable-value-field'
34
export { CredentialDetailHeading } from './components/credential-detail-heading'
45
export { CredentialDetailLayout } from './components/credential-detail-layout'

apps/sim/app/workspace/[workspaceId]/integrations/connected/[credentialId]/connected-credential-detail.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@ import {
1515
toast,
1616
} from '@/components/emcn'
1717
import { ArrowLeft } from '@/components/emcn/icons'
18+
import { cn } from '@/lib/core/utils/cn'
1819
import { writeOAuthReturnContext } from '@/lib/credentials/client-state'
1920
import { INTEGRATIONS } from '@/lib/integrations'
2021
import { getServiceConfigByProviderId } from '@/lib/oauth'
2122
import {
2223
AddPeopleModal,
24+
CHIP_FIELD_INPUT,
25+
CHIP_FIELD_SHELL,
2326
CopyableValueField,
2427
CredentialDetailHeading,
2528
CredentialDetailLayout,
@@ -263,21 +266,21 @@ export function ConnectedCredentialDetail({
263266
</DetailSection>
264267

265268
<DetailSection title='Display Name'>
266-
<div className='flex h-[30px] items-center gap-2 rounded-lg border border-[var(--border-1)] bg-[var(--surface-5)] px-2 dark:bg-[var(--surface-4)]'>
269+
<div className={CHIP_FIELD_SHELL}>
267270
<input
268271
id='credential-display-name'
269272
value={form.displayNameDraft}
270273
onChange={(event) => form.setDisplayNameDraft(event.target.value)}
271274
autoComplete='off'
272275
data-lpignore='true'
273276
disabled={!isAdmin}
274-
className='h-full w-full bg-transparent text-[var(--text-body)] text-sm outline-none placeholder:text-[var(--text-muted)] focus:outline-none disabled:cursor-not-allowed disabled:opacity-60'
277+
className={cn(CHIP_FIELD_INPUT, 'disabled:cursor-not-allowed disabled:opacity-60')}
275278
/>
276279
</div>
277280
</DetailSection>
278281

279282
<DetailSection title='Description'>
280-
<div className='flex items-start gap-1.5 rounded-lg border border-[var(--border-1)] bg-[var(--surface-5)] px-2 py-2 dark:bg-[var(--surface-4)]'>
283+
<div className={cn(CHIP_FIELD_SHELL, 'h-auto items-start py-2')}>
281284
<textarea
282285
id='credential-description'
283286
rows={4}
@@ -288,7 +291,10 @@ export function ConnectedCredentialDetail({
288291
autoComplete='off'
289292
data-lpignore='true'
290293
disabled={!isAdmin}
291-
className='w-full resize-none bg-transparent text-[var(--text-body)] text-sm outline-none placeholder:text-[var(--text-muted)] focus:outline-none disabled:cursor-not-allowed disabled:opacity-60'
294+
className={cn(
295+
CHIP_FIELD_INPUT,
296+
'h-auto resize-none disabled:cursor-not-allowed disabled:opacity-60'
297+
)}
292298
/>
293299
</div>
294300
</DetailSection>

apps/sim/app/workspace/[workspaceId]/settings/components/api-keys/api-keys.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -163,14 +163,14 @@ export function ApiKeys() {
163163
<div key={key.id} className='flex items-center justify-between gap-3'>
164164
<div className='flex min-w-0 flex-col justify-center gap-[1px]'>
165165
<div className='flex items-center gap-1.5'>
166-
<span className='max-w-[280px] truncate font-medium text-base'>
166+
<span className='max-w-[280px] truncate text-[14px] text-[var(--text-body)]'>
167167
{key.name}
168168
</span>
169169
<span className='text-[var(--text-secondary)] text-sm'>
170170
(last used: {formatLastUsed(key.lastUsed).toLowerCase()})
171171
</span>
172172
</div>
173-
<p className='truncate text-[var(--text-muted)] text-sm'>
173+
<p className='truncate text-[12px] text-[var(--text-muted)]'>
174174
{key.displayKey || key.key}
175175
</p>
176176
</div>
@@ -197,14 +197,14 @@ export function ApiKeys() {
197197
<div key={key.id} className='flex items-center justify-between gap-3'>
198198
<div className='flex min-w-0 flex-col justify-center gap-[1px]'>
199199
<div className='flex items-center gap-1.5'>
200-
<span className='max-w-[280px] truncate font-medium text-base'>
200+
<span className='max-w-[280px] truncate text-[14px] text-[var(--text-body)]'>
201201
{key.name}
202202
</span>
203203
<span className='text-[var(--text-secondary)] text-sm'>
204204
(last used: {formatLastUsed(key.lastUsed).toLowerCase()})
205205
</span>
206206
</div>
207-
<p className='truncate text-[var(--text-muted)] text-sm'>
207+
<p className='truncate text-[12px] text-[var(--text-muted)]'>
208208
{key.displayKey || key.key}
209209
</p>
210210
</div>
@@ -234,14 +234,14 @@ export function ApiKeys() {
234234
<div className='flex items-center justify-between gap-3'>
235235
<div className='flex min-w-0 flex-col justify-center gap-[1px]'>
236236
<div className='flex items-center gap-1.5'>
237-
<span className='max-w-[280px] truncate font-medium text-base'>
237+
<span className='max-w-[280px] truncate text-[14px] text-[var(--text-body)]'>
238238
{key.name}
239239
</span>
240240
<span className='text-[var(--text-secondary)] text-sm'>
241241
(last used: {formatLastUsed(key.lastUsed).toLowerCase()})
242242
</span>
243243
</div>
244-
<p className='truncate text-[var(--text-muted)] text-sm'>
244+
<p className='truncate text-[12px] text-[var(--text-muted)]'>
245245
{key.displayKey || key.key}
246246
</p>
247247
</div>

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

Lines changed: 97 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
TogetherIcon,
4343
WizaIcon,
4444
} from '@/components/icons'
45+
import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section'
4546
import {
4647
type BYOKKey,
4748
useBYOKKeys,
@@ -222,6 +223,45 @@ const PROVIDERS: {
222223
},
223224
]
224225

226+
/**
227+
* Provider groupings rendered as labeled sections. Every provider id in
228+
* {@link PROVIDERS} belongs to exactly one section; rows keep their
229+
* {@link PROVIDERS} order within each group.
230+
*/
231+
const PROVIDER_SECTIONS: { label: string; ids: BYOKProviderId[] }[] = [
232+
{
233+
label: 'Models',
234+
ids: [
235+
'openai',
236+
'anthropic',
237+
'google',
238+
'mistral',
239+
'fireworks',
240+
'together',
241+
'baseten',
242+
'ollama-cloud',
243+
'falai',
244+
],
245+
},
246+
{
247+
label: 'Search & web',
248+
ids: [
249+
'firecrawl',
250+
'exa',
251+
'serper',
252+
'linkup',
253+
'parallel_ai',
254+
'perplexity',
255+
'jina',
256+
'google_cloud',
257+
],
258+
},
259+
{
260+
label: 'Enrichment',
261+
ids: ['brandfetch', 'hunter', 'peopledatalabs', 'findymail', 'prospeo', 'wiza'],
262+
},
263+
]
264+
225265
export function BYOK() {
226266
const params = useParams()
227267
const workspaceId = (params?.workspaceId as string) || ''
@@ -249,6 +289,11 @@ export function BYOK() {
249289
)
250290
}, [searchTerm])
251291

292+
const filteredIds = useMemo(
293+
() => new Set(filteredProviders.map((p) => p.id)),
294+
[filteredProviders]
295+
)
296+
252297
const showNoResults = searchTerm.trim() && filteredProviders.length === 0
253298

254299
const getKeyForProvider = (providerId: BYOKProviderId): BYOKKey | undefined => {
@@ -307,44 +352,63 @@ export function BYOK() {
307352
disabled={isLoading}
308353
/>
309354

310-
{isLoading ? null : (
311-
<div className='flex flex-col gap-2'>
312-
{filteredProviders.map((provider) => {
313-
const existingKey = getKeyForProvider(provider.id)
314-
const Icon = provider.icon
355+
{isLoading ? null : showNoResults ? (
356+
<div className='py-4 text-center text-[var(--text-muted)] text-sm'>
357+
No providers found matching "{searchTerm}"
358+
</div>
359+
) : (
360+
<div className='flex flex-col gap-7'>
361+
{PROVIDER_SECTIONS.map((section) => {
362+
const rows = PROVIDERS.filter(
363+
(p) => section.ids.includes(p.id) && filteredIds.has(p.id)
364+
)
365+
if (rows.length === 0) return null
315366

316367
return (
317-
<div key={provider.id} className='flex items-center justify-between gap-3'>
318-
<div className='flex items-center gap-3'>
319-
<div className='flex size-9 flex-shrink-0 items-center justify-center overflow-hidden rounded-md bg-[var(--surface-6)]'>
320-
<Icon className='size-4' />
321-
</div>
322-
<div className='flex min-w-0 flex-col justify-center gap-[1px]'>
323-
<span className='font-medium text-base'>{provider.name}</span>
324-
<p className='truncate text-[var(--text-muted)] text-sm'>
325-
{provider.description}
326-
</p>
327-
</div>
328-
</div>
368+
<SettingsSection key={section.label} label={section.label}>
369+
<div className='flex flex-col gap-2'>
370+
{rows.map((provider) => {
371+
const existingKey = getKeyForProvider(provider.id)
372+
const Icon = provider.icon
373+
374+
return (
375+
<div
376+
key={provider.id}
377+
className='flex items-center justify-between gap-2.5'
378+
>
379+
<div className='flex min-w-0 items-center gap-2.5'>
380+
<div className='flex size-9 flex-shrink-0 items-center justify-center overflow-hidden rounded-xl border border-[var(--border-1)] bg-[var(--bg)]'>
381+
<Icon className='size-5' />
382+
</div>
383+
<div className='flex min-w-0 flex-col justify-center gap-[1px]'>
384+
<span className='truncate text-[14px] text-[var(--text-body)]'>
385+
{provider.name}
386+
</span>
387+
<span className='truncate text-[12px] text-[var(--text-muted)]'>
388+
{provider.description}
389+
</span>
390+
</div>
391+
</div>
329392

330-
{existingKey ? (
331-
<div className='flex flex-shrink-0 items-center gap-2'>
332-
<Chip onClick={() => openEditModal(provider.id)}>Update</Chip>
333-
<Chip onClick={() => setDeleteConfirmProvider(provider.id)}>Delete</Chip>
334-
</div>
335-
) : (
336-
<Chip variant='primary' onClick={() => openEditModal(provider.id)}>
337-
Add Key
338-
</Chip>
339-
)}
340-
</div>
393+
{existingKey ? (
394+
<div className='flex flex-shrink-0 items-center gap-2'>
395+
<Chip onClick={() => openEditModal(provider.id)}>Update</Chip>
396+
<Chip onClick={() => setDeleteConfirmProvider(provider.id)}>
397+
Delete
398+
</Chip>
399+
</div>
400+
) : (
401+
<Chip variant='primary' onClick={() => openEditModal(provider.id)}>
402+
Add Key
403+
</Chip>
404+
)}
405+
</div>
406+
)
407+
})}
408+
</div>
409+
</SettingsSection>
341410
)
342411
})}
343-
{showNoResults && (
344-
<div className='py-4 text-center text-[var(--text-muted)] text-sm'>
345-
No providers found matching "{searchTerm}"
346-
</div>
347-
)}
348412
</div>
349413
)}
350414
</div>

apps/sim/app/workspace/[workspaceId]/settings/components/copilot/copilot.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,14 +213,16 @@ export function Copilot() {
213213
<div key={key.id} className='flex items-center justify-between gap-3'>
214214
<div className='flex min-w-0 flex-col justify-center gap-[1px]'>
215215
<div className='flex items-center gap-1.5'>
216-
<span className='max-w-[280px] truncate font-medium text-base'>
216+
<span className='max-w-[280px] truncate text-[14px] text-[var(--text-body)]'>
217217
{key.name || 'Unnamed Key'}
218218
</span>
219219
<span className='text-[var(--text-secondary)] text-sm'>
220220
(last used: {formatLastUsed(key.lastUsed).toLowerCase()})
221221
</span>
222222
</div>
223-
<p className='truncate text-[var(--text-muted)] text-sm'>{key.displayKey}</p>
223+
<p className='truncate text-[12px] text-[var(--text-muted)]'>
224+
{key.displayKey}
225+
</p>
224226
</div>
225227
<Chip
226228
className='flex-shrink-0'

0 commit comments

Comments
 (0)