Skip to content

Commit 6c92816

Browse files
improvement(ui): reuse shared button and focus treatments
1 parent 7bf8fe3 commit 6c92816

12 files changed

Lines changed: 52 additions & 15 deletions

File tree

‎apps/sim/app/(interfaces)/chat/components/message-container/message-container.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ export function ChatMessageContainer({
7272
<Button
7373
onClick={scrollToBottom}
7474
size='sm'
75-
className='gap-1 rounded-full px-3 shadow-medium'
75+
shape='round'
76+
className='gap-1 px-3 shadow-medium'
7677
>
7778
<ArrowDown className='size-3.5' />
7879
<span className='sr-only'>Scroll to bottom</span>

‎apps/sim/app/(interfaces)/chat/components/message/components/file-download.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ export function ChatFileDownload({ file }: ChatFileDownloadProps) {
172172
variant='default'
173173
onClick={handleDownload}
174174
disabled={isDownloading}
175-
className='group flex h-auto w-[200px] gap-2 rounded-lg px-3 py-2'
175+
className='group flex w-[200px] gap-2 rounded-lg px-3 py-2'
176176
>
177177
<div className='flex size-8 shrink-0 items-center justify-center'>{renderIcon()}</div>
178178
<div className='min-w-0 flex-1 text-left'>

‎apps/sim/app/workspace/[workspaceId]/home/home.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -775,11 +775,11 @@ function HomeContent({ chatId, userName, userId }: HomeProps) {
775775
className={cn('z-30', RESOURCE_HEADER_CLASSES.overlay, RESOURCE_HEADER_CLASSES.endPosition)}
776776
>
777777
<Button
778-
variant='ghost'
778+
variant='quiet'
779779
size={null}
780780
type='button'
781781
onClick={isResourceCollapsed ? expandResource : collapseResource}
782-
className="after:-translate-x-1/2 after:-translate-y-1/2 relative size-[var(--resource-header-toggle-size)] rounded-[8px] after:absolute after:top-1/2 after:left-1/2 after:size-[var(--resource-header-toggle-hit-size)] after:content-[''] hover-hover:bg-[var(--surface-active)]"
782+
className="after:-translate-x-1/2 after:-translate-y-1/2 relative size-[var(--resource-header-toggle-size)] rounded-[8px] after:absolute after:top-1/2 after:left-1/2 after:size-[var(--resource-header-toggle-hit-size)] after:content-['']"
783783
aria-label={resourceToggleLabel}
784784
>
785785
<span className='relative'>

‎apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-sidebar-header/table-sidebar-header.tsx‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { type ButtonHTMLAttributes, forwardRef, type ReactNode } from 'react'
2-
import { Button, cn } from '@sim/emcn'
2+
import { Button } from '@sim/emcn'
33

44
interface TableSidebarHeaderProps {
55
children: ReactNode
@@ -28,10 +28,8 @@ export const TableSidebarHeaderAction = forwardRef<
2828
size='sm'
2929
iconSize='regular'
3030
iconPadding='sm'
31-
className={cn(
32-
'focus-visible:ring-2 focus-visible:ring-[color-mix(in_srgb,var(--text-muted)_30%,transparent)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--surface-2)]',
33-
className
34-
)}
31+
focusRing='muted'
32+
className={className}
3533
/>
3634
))
3735

‎apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/sidebar-row-actions.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { type ButtonHTMLAttributes, forwardRef, type ReactNode } from 'react'
2-
import { cn } from '@sim/emcn'
2+
import { cn, mutedFocusRingClass } from '@sim/emcn'
33

44
interface SidebarRowActionsProps {
55
children: ReactNode
@@ -62,7 +62,7 @@ export const SidebarRowAction = forwardRef<HTMLButtonElement, SidebarRowActionPr
6262
{...props}
6363
ref={ref}
6464
type='button'
65-
className='flex size-[18px] items-center justify-center rounded-sm focus-visible:ring-2 focus-visible:ring-[color-mix(in_srgb,var(--text-muted)_30%,transparent)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--surface-2)]'
65+
className={cn('flex size-[18px] items-center justify-center rounded-sm', mutedFocusRingClass)}
6666
/>
6767
)
6868
)

‎packages/emcn/src/components/button/button.test.tsx‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,26 @@ describe('Button shared action geometry', () => {
107107
expect(baseOnly).not.toContain('sm:size')
108108
})
109109

110+
it('offers the existing muted keyboard ring without changing the default', () => {
111+
const before = renderToStaticMarkup(
112+
<Button
113+
variant='ghost'
114+
size='sm'
115+
className='focus-visible:ring-2 focus-visible:ring-[color-mix(in_srgb,var(--text-muted)_30%,transparent)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--surface-2)]'
116+
>
117+
Open
118+
</Button>
119+
)
120+
const after = renderToStaticMarkup(
121+
<Button variant='ghost' size='sm' focusRing='muted'>
122+
Open
123+
</Button>
124+
)
125+
expect(normalizeClasses(after)).toBe(normalizeClasses(before))
126+
expect(after).not.toContain('focusRing=')
127+
expect(renderToStaticMarkup(<Button>Open</Button>)).not.toContain('focus-visible:ring-2')
128+
})
129+
110130
it('forwards refs and native focus, submission and disabled behavior with responsive sizing', () => {
111131
const container = document.createElement('div')
112132
document.body.appendChild(container)

‎packages/emcn/src/components/button/button.tsx‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { type ButtonHTMLAttributes, forwardRef } from 'react'
22
import { cva, type VariantProps } from 'class-variance-authority'
33
import { cn } from '../../lib/cn'
4+
import { mutedFocusRingClass } from '../../lib/focus-ring'
45

56
/**
67
* `size='icon'` is the square 20px icon-only button — a chip field's trailing
@@ -61,6 +62,9 @@ const buttonVariants = cva(
6162
shape: {
6263
round: 'rounded-full',
6364
},
65+
focusRing: {
66+
muted: mutedFocusRingClass,
67+
},
6468
iconPadding: {
6569
sm: 'p-1',
6670
md: 'p-1.5',
@@ -118,7 +122,7 @@ export interface ButtonProps
118122
}
119123

120124
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
121-
({ className, variant, size, iconSize, iconPadding, shape, ...props }, ref) => {
125+
({ className, variant, size, iconSize, iconPadding, shape, focusRing, ...props }, ref) => {
122126
const baseIconSize = typeof iconSize === 'object' ? iconSize?.base : iconSize
123127
const smIconSize = typeof iconSize === 'object' ? iconSize?.sm : undefined
124128
return (
@@ -131,6 +135,7 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
131135
iconSize: baseIconSize,
132136
iconPadding,
133137
shape,
138+
focusRing,
134139
}),
135140
smIconSize && responsiveIconSizes[smIconSize],
136141
className

‎packages/emcn/src/components/field-divider/field-divider.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,9 @@ function FieldDisclosure({ expanded, children, ...props }: FieldDisclosureProps)
7979
type='button'
8080
variant='ghost'
8181
size={null}
82+
focusRing='muted'
8283
aria-expanded={expanded}
83-
className='gap-1.5 whitespace-nowrap p-0 text-small focus-visible:ring-2 focus-visible:ring-[color-mix(in_srgb,var(--text-muted)_30%,transparent)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--surface-2)]'
84+
className='gap-1.5 whitespace-nowrap p-0 text-small'
8485
>
8586
{children}
8687
<ChevronDown

‎packages/emcn/src/components/slider/slider.tsx‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import * as React from 'react'
44
import * as SliderPrimitive from '@radix-ui/react-slider'
55
import { cn } from '../../lib/cn'
6+
import { mutedFocusRingClass } from '../../lib/focus-ring'
67

78
interface SliderProps extends React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root> {}
89

@@ -30,7 +31,12 @@ const Slider = React.forwardRef<React.ElementRef<typeof SliderPrimitive.Root>, S
3031
<SliderPrimitive.Track className='relative h-[6px] w-full grow overflow-hidden rounded-[20px] bg-[var(--border-1)] transition-colors'>
3132
<SliderPrimitive.Range className='absolute h-full bg-[var(--text-primary)]' />
3233
</SliderPrimitive.Track>
33-
<SliderPrimitive.Thumb className='relative block size-[14px] cursor-pointer rounded-full bg-[var(--text-primary)] shadow-xs transition-colors before:absolute before:inset-[-15px] before:content-[""] focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-[color-mix(in_srgb,var(--text-muted)_30%,transparent)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--surface-2)]' />
34+
<SliderPrimitive.Thumb
35+
className={cn(
36+
'relative block size-[14px] cursor-pointer rounded-full bg-[var(--text-primary)] shadow-xs transition-colors before:absolute before:inset-[-15px] before:content-[""] focus-visible:outline-hidden',
37+
mutedFocusRingClass
38+
)}
39+
/>
3440
</SliderPrimitive.Root>
3541
)
3642
)

‎packages/emcn/src/components/switch/switch.tsx‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import * as React from 'react'
44
import * as SwitchPrimitives from '@radix-ui/react-switch'
55
import { cn } from '../../lib/cn'
6+
import { mutedFocusRingClass } from '../../lib/focus-ring'
67

78
/**
89
* Switch component styled to match Sim's design system.
@@ -16,7 +17,8 @@ const Switch = React.memo(
1617
<SwitchPrimitives.Root
1718
disabled={disabled}
1819
className={cn(
19-
'peer relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full bg-[var(--border-1)] transition-colors before:absolute before:inset-[-12px] before:content-[""] focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-[color-mix(in_srgb,var(--text-muted)_30%,transparent)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--surface-2)] data-[disabled]:cursor-not-allowed data-[state=checked]:bg-[var(--text-primary)] data-[disabled]:opacity-50',
20+
'peer relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full bg-[var(--border-1)] transition-colors before:absolute before:inset-[-12px] before:content-[""] focus-visible:outline-hidden data-[disabled]:cursor-not-allowed data-[state=checked]:bg-[var(--text-primary)] data-[disabled]:opacity-50',
21+
mutedFocusRingClass,
2022
className
2123
)}
2224
{...props}

0 commit comments

Comments
 (0)