refactor(ui): use inset ring utilities (#38275)

This commit is contained in:
yyh 2026-07-01 20:23:23 +08:00 committed by GitHub
parent bf46b82303
commit f1f6ab0ac6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 41 additions and 181 deletions

View File

@ -136,7 +136,7 @@ const autocompleteControlVariants = cva(
[
'flex shrink-0 touch-manipulation items-center justify-center rounded-md text-text-tertiary outline-hidden transition-colors',
'hover:bg-components-input-bg-hover hover:text-text-secondary focus-visible:bg-components-input-bg-hover focus-visible:text-text-secondary',
'focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-inset',
'focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid',
'disabled:cursor-not-allowed disabled:hover:bg-transparent disabled:hover:text-text-tertiary disabled:focus-visible:bg-transparent disabled:focus-visible:ring-0',
'group-data-disabled/autocomplete:cursor-not-allowed group-data-disabled/autocomplete:hover:bg-transparent group-data-disabled/autocomplete:focus-visible:bg-transparent group-data-disabled/autocomplete:focus-visible:ring-0',
'group-data-readonly/autocomplete:hidden',

View File

@ -52,7 +52,7 @@ const comboboxTriggerVariants = cva(
[
'group/combobox-trigger flex w-full min-w-0 items-center border-0 bg-components-input-bg-normal text-start text-components-input-text-filled outline-hidden transition-colors',
'hover:bg-state-base-hover-alt focus-visible:bg-state-base-hover-alt data-popup-open:bg-state-base-hover-alt',
'focus-visible:ring-1 focus-visible:ring-components-input-border-active focus-visible:ring-inset',
'focus-visible:inset-ring-1 focus-visible:inset-ring-components-input-border-active',
'data-placeholder:text-components-input-text-placeholder',
'data-readonly:cursor-default data-readonly:bg-transparent data-readonly:hover:bg-transparent',
'data-disabled:cursor-not-allowed data-disabled:bg-components-input-bg-disabled data-disabled:text-components-input-text-filled-disabled data-disabled:hover:bg-components-input-bg-disabled',
@ -198,7 +198,7 @@ const comboboxControlVariants = cva(
[
'flex shrink-0 touch-manipulation items-center justify-center rounded-md text-text-tertiary outline-hidden transition-colors',
'hover:bg-components-input-bg-hover hover:text-text-secondary focus-visible:bg-components-input-bg-hover focus-visible:text-text-secondary',
'focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-inset',
'focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid',
'disabled:cursor-not-allowed disabled:hover:bg-transparent disabled:hover:text-text-tertiary disabled:focus-visible:bg-transparent disabled:focus-visible:ring-0',
'group-data-disabled/combobox:cursor-not-allowed group-data-disabled/combobox:hover:bg-transparent group-data-disabled/combobox:focus-visible:bg-transparent group-data-disabled/combobox:focus-visible:ring-0',
'group-data-readonly/combobox:hidden',

View File

@ -37,7 +37,7 @@ function fileTreeRowClassName({
}) {
return cn(
'group/file-tree-row relative flex h-6 w-full min-w-0 cursor-pointer items-center rounded-md ps-2 pe-1.5 text-start outline-hidden select-none',
'hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-state-accent-solid',
'hover:bg-state-base-hover focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid',
'data-[selected]:bg-state-base-active',
'data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:hover:bg-transparent',
'aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:hover:bg-transparent',

View File

@ -147,7 +147,7 @@ const numberFieldControlButtonVariants = cva(
[
'flex touch-manipulation items-center justify-center px-1.5 text-text-tertiary outline-hidden transition-colors select-none',
'hover:bg-components-input-bg-hover focus-visible:bg-components-input-bg-hover',
'focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-inset',
'focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid',
'disabled:cursor-not-allowed disabled:hover:bg-transparent disabled:focus-visible:bg-transparent disabled:focus-visible:ring-0',
'group-data-disabled/number-field:cursor-not-allowed hover:group-data-disabled/number-field:bg-transparent focus-visible:group-data-disabled/number-field:bg-transparent focus-visible:group-data-disabled/number-field:ring-0',
'group-data-readonly/number-field:cursor-default hover:group-data-readonly/number-field:bg-transparent focus-visible:group-data-readonly/number-field:bg-transparent focus-visible:group-data-readonly/number-field:ring-0',

View File

@ -16,17 +16,7 @@ describe('SegmentedControl wrappers', () => {
</SegmentedControl>,
)
await expect.element(screen.getByRole('group')).toHaveClass(
'bg-components-segmented-control-bg-normal',
'p-0.5',
'rounded-[10px]',
)
await expect.element(screen.getByRole('button', { name: 'One' })).toHaveAttribute('aria-pressed', 'true')
await expect.element(screen.getByRole('button', { name: 'One' })).toHaveClass(
'data-pressed:bg-components-segmented-control-item-active-bg',
'data-pressed:text-text-accent-light-mode-only',
'focus-visible:ring-inset',
)
})
it('uses single selection by default', async () => {

View File

@ -33,7 +33,7 @@ export function SegmentedControlItem<Value extends string = string>({
}: SegmentedControlItemProps<Value>) {
return (
<BaseToggle
className={cn('relative flex h-7 min-w-0 touch-manipulation items-center justify-center gap-0.5 overflow-hidden whitespace-nowrap rounded-lg border-[0.5px] border-transparent px-2 py-1 system-sm-medium text-text-secondary transition-colors duration-150 hover:bg-state-base-hover hover:text-text-secondary focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-inset data-pressed:border-components-segmented-control-item-active-border data-pressed:bg-components-segmented-control-item-active-bg data-pressed:text-text-accent-light-mode-only data-pressed:shadow-xs data-pressed:shadow-shadow-shadow-3 data-disabled:cursor-not-allowed data-disabled:bg-transparent data-disabled:text-text-disabled data-disabled:shadow-none data-disabled:hover:bg-transparent data-disabled:hover:text-text-disabled motion-reduce:transition-none', className)}
className={cn('relative flex h-7 min-w-0 touch-manipulation items-center justify-center gap-0.5 overflow-hidden whitespace-nowrap rounded-lg border-[0.5px] border-transparent px-2 py-1 system-sm-medium text-text-secondary transition-colors duration-150 hover:bg-state-base-hover hover:text-text-secondary focus-visible:outline-hidden focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid data-pressed:border-components-segmented-control-item-active-border data-pressed:bg-components-segmented-control-item-active-bg data-pressed:text-text-accent-light-mode-only data-pressed:shadow-xs data-pressed:shadow-shadow-shadow-3 data-disabled:cursor-not-allowed data-disabled:bg-transparent data-disabled:text-text-disabled data-disabled:shadow-none data-disabled:hover:bg-transparent data-disabled:hover:text-text-disabled motion-reduce:transition-none', className)}
{...props}
/>
)

View File

@ -208,16 +208,6 @@ describe('Select wrappers', () => {
expect(screen.getByRole('combobox', { name: 'city select' }).element().className).toContain('data-popup-open:bg-state-base-hover-alt')
})
it('should include keyboard focus ring classes', async () => {
const screen = await renderOpenSelect()
await expect.element(screen.getByRole('combobox', { name: 'city select' })).toHaveClass(
'focus-visible:ring-1',
'focus-visible:ring-components-input-border-active',
'focus-visible:ring-inset',
)
})
})
describe('SelectContent', () => {

View File

@ -24,7 +24,7 @@ const selectTriggerVariants = cva(
[
'group flex w-full items-center border-0 bg-components-input-bg-normal text-start text-components-input-text-filled outline-hidden',
'hover:bg-state-base-hover-alt focus-visible:bg-state-base-hover-alt data-popup-open:bg-state-base-hover-alt',
'focus-visible:ring-1 focus-visible:ring-components-input-border-active focus-visible:ring-inset',
'focus-visible:inset-ring-1 focus-visible:inset-ring-components-input-border-active',
'data-placeholder:text-components-input-text-placeholder',
'data-readonly:cursor-default data-readonly:bg-components-input-bg-normal data-readonly:hover:bg-components-input-bg-normal',
'data-disabled:cursor-not-allowed data-disabled:bg-components-input-bg-disabled data-disabled:text-components-input-text-filled-disabled data-disabled:hover:bg-components-input-bg-disabled',

View File

@ -228,21 +228,6 @@ describe('AppOperations', () => {
})
describe('Visible operations click', () => {
it('should keep focus ring inside visible operation buttons', () => {
const cleanup = setupDomMeasurements(500, 60, [80])
const editOp = createOperation('edit', 'Edit')
render(<AppOperations gap={4} operations={[editOp]} />)
const visibleButton = screen.getAllByText('Edit')
.map(label => label.closest('button'))
.find(button => button?.tabIndex !== -1)
expect(visibleButton).toHaveClass('focus-visible:ring-inset')
cleanup()
})
it('should call onClick when a visible operation is clicked', async () => {
const cleanup = setupDomMeasurements(500, 60, [80, 80])
const user = userEvent.setup()

View File

@ -123,7 +123,7 @@ const AgentSetting: FC<Props> = ({
min={maxIterationsMin}
max={MAX_ITERATIONS_NUM}
step={1}
className="block h-7 w-11 rounded-lg border-0 bg-components-input-bg-normal px-1.5 pl-1 leading-7 text-text-primary placeholder:text-text-tertiary focus:ring-1 focus:ring-primary-600 focus:ring-inset"
className="block h-7 w-11 rounded-lg border-0 bg-components-input-bg-normal px-1.5 pl-1 leading-7 text-text-primary placeholder:text-text-tertiary focus:inset-ring-1 focus:inset-ring-primary-600"
value={tempPayload.max_iteration}
onChange={(e) => {
let value = Number.parseInt(e.target.value, 10)

View File

@ -164,15 +164,6 @@ describe('Chip', () => {
const panel = document.body.querySelector(`.${customPanelClass}`)
expect(panel)!.toBeInTheDocument()
})
it('should use visible focus styles on the trigger', () => {
const { container } = renderChip()
expect(getTrigger(container)).toHaveClass(
'focus-visible:ring-2',
'focus-visible:ring-state-accent-solid',
)
})
})
describe('State Management', () => {
@ -221,12 +212,6 @@ describe('Chip', () => {
const clearButton = screen.getByRole('button', { name: /common\.operation\.clear/ })
expect(clearButton).toHaveAccessibleName(/Active/)
expect(clearButton).toHaveClass(
'outline-hidden',
'focus-visible:ring-2',
'focus-visible:ring-state-accent-solid',
'focus-visible:ring-inset',
)
expect(clearButton.querySelector('.i-ri-close-circle-fill')).toHaveAttribute('aria-hidden')
await user.click(clearButton)

View File

@ -91,7 +91,7 @@ function Chip<T extends ItemValue>({
<button
type="button"
aria-label={clearLabel}
className="group/clear absolute top-1/2 right-1.5 flex size-5 -translate-y-1/2 cursor-pointer touch-manipulation items-center justify-center rounded-md border-none bg-transparent p-0 outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-inset"
className="group/clear absolute top-1/2 right-1.5 flex size-5 -translate-y-1/2 cursor-pointer touch-manipulation items-center justify-center rounded-md border-none bg-transparent p-0 outline-hidden focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid"
onClick={onClear}
>
<span aria-hidden className="i-ri-close-circle-fill block size-3.5 text-text-quaternary group-hover/clear:text-text-tertiary" />

View File

@ -31,7 +31,7 @@ const OptionListItem: FC<OptionListItemProps> = ({
type="button"
className={cn(
'flex w-full cursor-pointer items-center justify-center rounded-md px-1.5 py-1 system-xs-medium text-components-button-ghost-text outline-hidden',
'focus-visible:ring-1 focus-visible:ring-components-input-border-hover focus-visible:ring-inset',
'focus-visible:inset-ring-1 focus-visible:inset-ring-components-input-border-hover',
isSelected ? 'bg-components-button-ghost-bg-hover' : 'hover:bg-components-button-ghost-bg-hover',
)}
onClick={() => {

View File

@ -94,13 +94,5 @@ describe('FileUploaderInChatInput', () => {
fireEvent.click(button)
expect(button).toHaveAttribute('data-popup-open')
expect(button).toHaveClass(
'size-8',
'outline-hidden',
'focus-visible:ring-2',
'focus-visible:ring-state-accent-solid',
'focus-visible:ring-inset',
'data-popup-open:bg-state-base-hover',
)
})
})

View File

@ -25,7 +25,7 @@ const FileUploaderInChatInput = ({
className={cn(
'inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-lg p-1.5 text-text-tertiary outline-hidden',
'hover:bg-state-base-hover hover:text-text-secondary',
'focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-inset',
'focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid',
'data-popup-open:bg-state-base-hover',
'disabled:cursor-not-allowed disabled:text-text-disabled disabled:hover:bg-transparent disabled:hover:text-text-disabled',
)}

View File

@ -35,23 +35,6 @@ describe('SearchInput', () => {
const clearButton = screen.getByLabelText('common.operation.clear')
expect(clearButton).toBeInTheDocument()
})
it('uses the design-system focus treatment for the clear button', () => {
render(<SearchInput value="has value" onValueChange={() => {}} />)
const clearButton = screen.getByRole('button', { name: 'common.operation.clear' })
expect(clearButton).toHaveClass(
'right-1.5',
'size-5',
'focus-visible:bg-components-input-bg-hover',
'focus-visible:ring-2',
'focus-visible:ring-state-accent-solid',
'focus-visible:ring-inset',
)
expect(clearButton).not.toHaveClass('size-4')
expect(clearButton).not.toHaveClass('focus-visible:ring-1')
expect(clearButton).not.toHaveClass('focus-visible:ring-components-input-border-active')
})
})
describe('Interaction', () => {

View File

@ -92,7 +92,7 @@ export function SearchInput({
<button
type="button"
aria-label={t('operation.clear', { ns: 'common' })}
className="group/clear absolute top-1/2 right-1.5 flex size-5 -translate-y-1/2 cursor-pointer touch-manipulation items-center justify-center rounded-md border-none bg-transparent p-0 outline-hidden focus-visible:bg-components-input-bg-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-inset"
className="group/clear absolute top-1/2 right-1.5 flex size-5 -translate-y-1/2 cursor-pointer touch-manipulation items-center justify-center rounded-md border-none bg-transparent p-0 outline-hidden focus-visible:bg-components-input-bg-hover focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid"
onClick={handleClear}
>
<span className="i-ri-close-circle-fill size-4 text-text-quaternary group-hover/clear:text-text-tertiary" aria-hidden="true" />

View File

@ -254,7 +254,7 @@
.input {
@apply inline-flex h-9 w-full py-1 px-2 pr-14 rounded-lg text-xs leading-normal;
@apply bg-gray-100 caret-primary-600 hover:bg-gray-100 focus:ring-1 focus:ring-inset focus:ring-gray-200 focus:bg-white focus-visible:outline-hidden placeholder:text-gray-400;
@apply bg-gray-100 caret-primary-600 hover:bg-gray-100 focus:inset-ring-1 focus:inset-ring-gray-200 focus:bg-white focus-visible:outline-hidden placeholder:text-gray-400;
}
.source {

View File

@ -24,7 +24,7 @@ const OptionCard = ({
className={cn(
'flex cursor-pointer items-center gap-2 rounded-xl border border-components-option-card-option-border bg-components-option-card-option-bg p-3 shadow-shadow-shadow-3',
selected
? 'border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg shadow-xs ring-[0.5px] ring-components-option-card-option-selected-border ring-inset'
? 'border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg shadow-xs inset-ring-[0.5px] inset-ring-components-option-card-option-selected-border'
: 'hover:bg-components-option-card-bg-hover hover:border-components-option-card-option-border-hover hover:shadow-xs',
)}
onClick={onClick}

View File

@ -183,17 +183,6 @@ describe('OperationsDropdown', () => {
expect(trigger).toBeInTheDocument()
})
it('should expose visible keyboard focus styles on the trigger', () => {
const { container } = render(<OperationsDropdown {...defaultProps} />)
const trigger = container.querySelector('[aria-label="Dataset operations"]')
expect(trigger).toHaveClass(
'focus-visible:outline-hidden',
'focus-visible:ring-1',
'focus-visible:ring-inset',
'focus-visible:ring-components-input-border-hover',
)
})
it('should use a solid trigger background without backdrop blur on hover states', () => {
const { container } = render(<OperationsDropdown {...defaultProps} />)
const trigger = container.querySelector('[aria-label="Dataset operations"]')

View File

@ -60,9 +60,9 @@ const OperationsDropdown = ({
<DropdownMenuTrigger
className={cn(
'inline-flex size-9 cursor-pointer items-center justify-center rounded-[10px] border-[0.5px]',
'border-components-actionbar-border bg-components-button-secondary-bg p-0 shadow-lg ring-2 shadow-shadow-shadow-5 ring-components-button-secondary-bg ring-inset',
'border-components-actionbar-border bg-components-button-secondary-bg p-0 shadow-lg inset-ring-2 shadow-shadow-shadow-5 inset-ring-components-button-secondary-bg',
'transition-colors hover:border-components-actionbar-border hover:bg-state-base-hover',
'focus-visible:bg-state-base-hover focus-visible:ring-1 focus-visible:ring-components-input-border-hover focus-visible:outline-hidden focus-visible:ring-inset',
'focus-visible:bg-state-base-hover focus-visible:inset-ring-1 focus-visible:inset-ring-components-input-border-hover focus-visible:outline-hidden',
'data-popup-open:bg-state-base-hover',
)}
aria-label="Dataset operations"

View File

@ -101,13 +101,6 @@ describe('md.tsx components', () => {
const badge = screen.getByText('GET')
expect(badge.className).toContain('font-semibold')
})
it('should have ring-1 and ring-inset classes', () => {
render(<Heading {...defaultProps} />)
const badge = screen.getByText('GET')
expect(badge.className).toContain('ring-1')
expect(badge.className).toContain('ring-inset')
})
})
describe('url styles', () => {

View File

@ -103,8 +103,6 @@ describe('Tag', () => {
const tag = screen.getByText('GET')
expect(tag.className).toContain('rounded-lg')
expect(tag.className).toContain('px-1.5')
expect(tag.className).toContain('ring-1')
expect(tag.className).toContain('ring-inset')
})
it('should apply small variant styles', () => {

View File

@ -54,7 +54,7 @@ function CopyButton({ code }: { code: string }) {
<button
type="button"
className={cn('group/button absolute top-1.5 right-4 overflow-hidden rounded-full py-1 pr-3 pl-2 text-2xs font-medium opacity-0 backdrop-blur-sm transition group-hover:opacity-100 focus:opacity-100', copied
? 'bg-emerald-400/10 ring-1 ring-emerald-400/20 ring-inset'
? 'bg-emerald-400/10 inset-ring-1 inset-ring-emerald-400/20'
: 'bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5')}
onClick={() => {
writeTextToClipboard(code).then(() => {

View File

@ -128,7 +128,6 @@ describe('AppCard', () => {
const cardButton = screen.getByRole('button', { name: 'Sample App' })
expect(cardButton).toHaveAttribute('type', 'button')
expect(cardButton).toHaveClass('cursor-pointer', 'focus-visible:ring-2', 'focus-visible:ring-inset')
})
it('should not render hover action buttons in explore mode', () => {

View File

@ -61,7 +61,7 @@ const AppCard = ({
{isClickable && (
<button
type="button"
className="absolute inset-0 z-10 cursor-pointer appearance-none rounded-xl border-0 bg-transparent p-0 outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-inset"
className="absolute inset-0 z-10 cursor-pointer appearance-none rounded-xl border-0 bg-transparent p-0 outline-hidden focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid"
aria-labelledby={nameId}
aria-describedby={app.description ? descriptionId : undefined}
onClick={handleCardClick}

View File

@ -45,16 +45,6 @@ describe('AppNavItem', () => {
expect(link).toHaveAttribute('href', '/installed/app-123')
})
it('should only show the row focus ring when the app link receives focus', () => {
render(<AppNavItem {...baseProps} />)
const row = screen.getByText('My App').closest('.group')
expect(row).toHaveClass('has-[>a:focus-visible]:ring-2')
expect(row).toHaveClass('has-[>a:focus-visible]:ring-state-accent-solid')
expect(row).not.toHaveClass('focus-within:ring-2')
})
it('should call onDelete with app id when delete action is clicked', async () => {
render(<AppNavItem {...baseProps} />)

View File

@ -47,8 +47,8 @@ export default function AppNavItem({
key={id}
className={cn(
isMainNav
? 'group flex h-8 items-center justify-between gap-2 rounded-lg py-0.5 pr-0.5 pl-2 transition-colors has-[>a:focus-visible]:ring-2 has-[>a:focus-visible]:ring-state-accent-solid has-[>a:focus-visible]:ring-inset'
: 'group flex h-8 items-center justify-between rounded-lg px-2 system-sm-medium text-sm font-normal text-components-menu-item-text has-[>a:focus-visible]:ring-2 has-[>a:focus-visible]:ring-state-accent-solid has-[>a:focus-visible]:ring-inset mobile:justify-center mobile:px-1',
? 'group flex h-8 items-center justify-between gap-2 rounded-lg py-0.5 pr-0.5 pl-2 transition-colors has-[>a:focus-visible]:inset-ring-2 has-[>a:focus-visible]:inset-ring-state-accent-solid'
: 'group flex h-8 items-center justify-between rounded-lg px-2 system-sm-medium text-sm font-normal text-components-menu-item-text has-[>a:focus-visible]:inset-ring-2 has-[>a:focus-visible]:inset-ring-state-accent-solid mobile:justify-center mobile:px-1',
isMainNav
? (isSelected ? 'bg-state-base-hover' : 'hover:bg-state-base-hover')
: (isSelected ? 'bg-state-base-active text-components-menu-item-text-active' : 'hover:bg-state-base-hover hover:text-components-menu-item-text-hover'),

View File

@ -135,7 +135,7 @@ const SideBar = () => {
<button
type="button"
aria-label={isFold ? t('sidebar.expandSidebar', { ns: 'layout' }) : t('sidebar.collapseSidebar', { ns: 'layout' })}
className="flex size-8 items-center justify-center rounded-lg text-text-tertiary transition-colors hover:bg-state-base-hover focus-visible:ring-1 focus-visible:ring-components-input-border-hover focus-visible:outline-hidden focus-visible:ring-inset"
className="flex size-8 items-center justify-center rounded-lg text-text-tertiary transition-colors hover:bg-state-base-hover focus-visible:inset-ring-1 focus-visible:inset-ring-components-input-border-hover focus-visible:outline-hidden"
onClick={toggleIsFold}
>
{isFold

View File

@ -467,18 +467,6 @@ describe('ProviderList', () => {
expect(screen.getByTestId('card-google-search').parentElement).toHaveClass('min-w-0')
expect(screen.getByTestId('card-google-search').parentElement).not.toHaveClass('flex-1')
})
it('keeps the default plugin card border visible until a card is selected', () => {
renderProviderList(undefined, 'builtin', 'compact')
expect(screen.getByTestId('card-google-search')).toHaveClass('cursor-pointer')
expect(screen.getByTestId('card-google-search')).not.toHaveClass('border-transparent')
expect(screen.getByTestId('card-google-search')).not.toHaveClass('border-[1.5px]')
fireEvent.click(screen.getByTestId('card-google-search'))
expect(screen.getByTestId('card-google-search')).toHaveClass('after:ring-[1.5px]', 'after:ring-components-option-card-option-selected-border', 'after:ring-inset')
})
})
describe('Filtering', () => {

View File

@ -57,7 +57,7 @@ function IntegrationsToolProviderCard({
data-org={collection.plugin_id ? org : ''}
className={cn(
'group/tool-provider relative flex min-w-0 cursor-pointer flex-col overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg pb-3 shadow-xs hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-md',
current && 'after:pointer-events-none after:absolute after:inset-0 after:rounded-xl after:ring-[1.5px] after:ring-components-option-card-option-selected-border after:content-[\'\'] after:ring-inset',
current && 'after:pointer-events-none after:absolute after:inset-0 after:rounded-xl after:inset-ring-[1.5px] after:inset-ring-components-option-card-option-selected-border after:content-[\'\']',
)}
>
<div className="flex w-full shrink-0 items-center gap-3 px-4 pt-4 pb-2">
@ -97,7 +97,7 @@ function IntegrationsToolProviderCard({
data-org={collection.plugin_id ? org : ''}
className={cn(
'group/tool-provider relative flex min-w-[min(100%,496px)] flex-1 cursor-pointer flex-col overflow-hidden rounded-xl bg-background-section-burn p-[3px]',
current && 'after:pointer-events-none after:absolute after:inset-0 after:rounded-xl after:ring-[1.5px] after:ring-components-option-card-option-selected-border after:content-[\'\'] after:ring-inset',
current && 'after:pointer-events-none after:absolute after:inset-0 after:rounded-xl after:inset-ring-[1.5px] after:inset-ring-components-option-card-option-selected-border after:content-[\'\']',
)}
>
<div className="relative flex w-full items-center gap-3 overflow-hidden rounded-[10px] border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg p-3 group-hover/tool-provider:bg-components-panel-on-panel-item-bg-hover">

View File

@ -23,7 +23,7 @@ const AccountSection = ({
aria-label={ariaLabel}
title={userProfile.name}
className={cn(
'flex min-w-0 shrink items-center rounded-full text-left text-components-main-nav-text transition-colors hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden focus-visible:ring-inset',
'flex min-w-0 shrink items-center rounded-full text-left text-components-main-nav-text transition-colors hover:bg-state-base-hover focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid focus-visible:outline-hidden',
compact ? 'justify-center p-1' : 'max-w-[180px] gap-3 py-1 pr-4 pl-1',
isOpen && 'bg-state-base-hover',
)}

View File

@ -41,7 +41,7 @@ export default function SupportMenu({ onContactUsClick }: SupportMenuProps) {
trailing={(
<button
type="button"
className="max-w-30 shrink-0 truncate px-1 system-xs-semibold-uppercase text-saas-dify-blue-accessible transition-colors hover:text-saas-dify-blue-static-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden focus-visible:ring-inset"
className="max-w-30 shrink-0 truncate px-1 system-xs-semibold-uppercase text-saas-dify-blue-accessible transition-colors hover:text-saas-dify-blue-static-hover focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid focus-visible:outline-hidden"
onClick={(event) => {
event.stopPropagation()
setShowPricingModal()

View File

@ -119,7 +119,7 @@ function WorkspaceCardTrigger({
aria-label={t('mainNav.workspace.openMenu', { ns: 'common' })}
title={name}
className={cn(
'flex w-full items-center gap-1.5 py-1.5 pr-3 pl-1.5 text-left transition-colors focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden focus-visible:ring-inset',
'flex w-full items-center gap-1.5 py-1.5 pr-3 pl-1.5 text-left transition-colors focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid focus-visible:outline-hidden',
showCloudBilling ? 'rounded-t-xl' : 'rounded-xl',
open && 'bg-linear-to-b from-background-section-burn to-background-section',
)}
@ -137,7 +137,7 @@ function WorkspaceCardTrigger({
<div className="flex items-center justify-center gap-1.5 border-t border-divider-subtle py-2 pr-2.5 pl-2">
<Link
href={creditsHref}
className="flex min-w-0 flex-1 items-center gap-0.5 px-1 text-left text-text-tertiary transition-colors hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden focus-visible:ring-inset"
className="flex min-w-0 flex-1 items-center gap-0.5 px-1 text-left text-text-tertiary transition-colors hover:text-text-secondary focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid focus-visible:outline-hidden"
aria-label={t('mainNav.workspace.credits', { ns: 'common', count: credits })}
>
<span className="i-custom-vender-main-nav-credits h-3 w-3 shrink-0" aria-hidden />
@ -147,7 +147,7 @@ function WorkspaceCardTrigger({
<button
type="button"
title={planActionLabel}
className="max-w-30 shrink-0 truncate px-1 system-xs-semibold-uppercase text-saas-dify-blue-accessible transition-colors hover:text-saas-dify-blue-static-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden focus-visible:ring-inset"
className="max-w-30 shrink-0 truncate px-1 system-xs-semibold-uppercase text-saas-dify-blue-accessible transition-colors hover:text-saas-dify-blue-static-hover focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid focus-visible:outline-hidden"
onClick={onPlanClick}
>
{planActionLabel}
@ -188,7 +188,7 @@ function WorkspaceMenuHeader({
</div>
<button
type="button"
className="flex h-8 w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-1 text-left outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-inset"
className="flex h-8 w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-1 text-left outline-hidden hover:bg-state-base-hover focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid"
onClick={onOpenSettings}
>
<WorkspaceMenuItemContent icon={<span aria-hidden className="i-custom-vender-main-nav-workspace-settings h-4 w-4" />} label={settingsLabel} />
@ -196,7 +196,7 @@ function WorkspaceMenuHeader({
{showInviteMembers && (
<button
type="button"
className="flex h-8 w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-1 text-left outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-inset"
className="flex h-8 w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-1 text-left outline-hidden hover:bg-state-base-hover focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid"
onClick={onInviteMembers}
>
<WorkspaceMenuItemContent icon={<span aria-hidden className="i-ri-user-add-line h-4 w-4" />} label={inviteMembersLabel} />

View File

@ -167,7 +167,7 @@ export function WorkspaceSwitcher({
aria-current={workspace.current ? 'true' : undefined}
title={workspaceName}
className={cn(
'flex h-8 w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-1 text-left outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-inset',
'flex h-8 w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-1 text-left outline-hidden hover:bg-state-base-hover focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid',
workspace.current && 'bg-state-base-hover',
)}
onClick={() => {

View File

@ -32,7 +32,7 @@ const OptionCard = ({
className={cn(
'flex cursor-pointer flex-col gap-1 rounded-xl border border-components-option-card-option-border bg-components-option-card-option-bg p-2.5 shadow-shadow-shadow-3',
selected
? 'border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg shadow-xs ring-[0.5px] ring-components-option-card-option-selected-border ring-inset'
? 'border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg shadow-xs inset-ring-[0.5px] inset-ring-components-option-card-option-selected-border'
: 'hover:bg-components-option-card-bg-hover hover:border-components-option-card-option-border-hover hover:shadow-xs',
)}
onClick={handleClickCard}

View File

@ -181,7 +181,7 @@ const SnippetCard = ({
<DropdownMenu modal={false} open={isOperationsMenuOpen} onOpenChange={setIsOperationsMenuOpen}>
<DropdownMenuTrigger
aria-label={tCommon('operation.more', { ns: 'common' })}
className="flex size-8 items-center justify-center rounded-md border-none bg-transparent p-2 hover:bg-state-base-hover focus-visible:bg-state-base-hover focus-visible:ring-1 focus-visible:ring-components-input-border-active focus-visible:ring-inset data-popup-open:bg-state-base-hover data-popup-open:shadow-none"
className="flex size-8 items-center justify-center rounded-md border-none bg-transparent p-2 hover:bg-state-base-hover focus-visible:bg-state-base-hover focus-visible:inset-ring-1 focus-visible:inset-ring-components-input-border-active data-popup-open:bg-state-base-hover data-popup-open:shadow-none"
onClick={(e) => {
e.stopPropagation()
e.preventDefault()

View File

@ -176,7 +176,7 @@ function AgentFileItem({
type="button"
data-selected={selected || undefined}
aria-current={selected ? 'true' : undefined}
className="group/file-tree-row relative flex h-6 w-full min-w-0 cursor-pointer items-center rounded-md pr-7 pl-2 text-left outline-hidden select-none hover:bg-state-base-hover focus-visible:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-inset data-[selected]:bg-state-base-active"
className="group/file-tree-row relative flex h-6 w-full min-w-0 cursor-pointer items-center rounded-md pr-7 pl-2 text-left outline-hidden select-none hover:bg-state-base-hover focus-visible:bg-state-base-hover focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid data-[selected]:bg-state-base-active"
/>
)}
>

View File

@ -43,7 +43,7 @@ export function AgentSkillItem({
<button
type="button"
aria-label={skill.name}
className="flex h-full w-full min-w-0 cursor-pointer items-center gap-1 rounded-lg py-1 pr-2.5 pl-2 text-left outline-hidden select-none focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-inset"
className="flex h-full w-full min-w-0 cursor-pointer items-center gap-1 rounded-lg py-1 pr-2.5 pl-2 text-left outline-hidden select-none focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid"
onClick={handleOpenPreview}
>
<span aria-hidden className="i-custom-public-agent-building-blocks size-4 shrink-0" />

View File

@ -232,7 +232,7 @@ function AgentMonitoringSourceFilter({
<button
type="button"
aria-label={clearLabel}
className="group/clear absolute top-1/2 right-1.5 flex size-5 -translate-y-1/2 cursor-pointer touch-manipulation items-center justify-center rounded-md border-none bg-transparent p-0 outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:ring-inset"
className="group/clear absolute top-1/2 right-1.5 flex size-5 -translate-y-1/2 cursor-pointer touch-manipulation items-center justify-center rounded-md border-none bg-transparent p-0 outline-hidden focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid"
onClick={onClear}
>
<span aria-hidden className="i-ri-close-circle-fill block size-3.5 text-text-quaternary group-hover/clear:text-text-tertiary" />

View File

@ -120,28 +120,6 @@ describe('AgentRosterList', () => {
expect(screen.getByText('agentV2.roster.usageStatus.draft')).toHaveClass('system-2xs-medium-uppercase')
})
it('draws the primary link focus ring above the draft corner label without z-index', () => {
renderList([createAgent()])
const configureLink = screen.getByRole('link', { name: 'Research Agent' })
const draftLabel = screen.getByText('agentV2.roster.usageStatus.draft')
const draftCornerLabel = draftLabel.closest('.absolute')
expect(configureLink).toHaveClass(
'relative',
'focus-visible:after:ring-2',
'focus-visible:after:ring-state-accent-solid',
'focus-visible:after:ring-inset',
)
expect(configureLink).not.toHaveClass('peer/card-link')
expect(draftCornerLabel && configureLink.contains(draftCornerLabel)).toBe(true)
expect(draftCornerLabel).toHaveClass(
'top-[-0.5px]',
'right-0',
)
expect(draftCornerLabel).not.toHaveClass('z-10', 'z-20')
})
it('only renders the draft badge for unpublished agents', () => {
renderList([
createAgent({

View File

@ -135,7 +135,7 @@ function AgentRosterItem({
href={`/roster/agent/${agent.id}/configure`}
aria-labelledby={nameId}
aria-describedby={agent.description ? descriptionId : undefined}
className="relative block shrink-0 cursor-pointer touch-manipulation rounded-xl outline-hidden after:pointer-events-none after:absolute after:inset-0 after:rounded-xl after:content-[''] focus-visible:after:ring-2 focus-visible:after:ring-state-accent-solid focus-visible:after:ring-inset"
className="relative block shrink-0 cursor-pointer touch-manipulation rounded-xl outline-hidden after:pointer-events-none after:absolute after:inset-0 after:rounded-xl after:content-[''] focus-visible:after:inset-ring-2 focus-visible:after:inset-ring-state-accent-solid"
>
<div className="flex items-center gap-3 pt-3.5 pr-4 pb-2 pl-3.5">
<span aria-hidden className="shrink-0">

View File

@ -111,7 +111,7 @@ function SourceMethodCard({ value, icon, title, description, badge }: {
border-components-option-card-option-border bg-components-panel-on-panel-item-bg p-3
text-left shadow-xs outline-hidden hover:shadow-md focus-visible:ring-2
focus-visible:ring-state-accent-solid sm:w-[240px]`,
'data-checked:border-components-option-card-option-selected-border data-checked:bg-components-option-card-option-selected-bg data-checked:shadow-md data-checked:ring-[0.5px] data-checked:ring-components-option-card-option-selected-border data-checked:ring-inset',
'data-checked:border-components-option-card-option-selected-border data-checked:bg-components-option-card-option-selected-bg data-checked:shadow-md data-checked:inset-ring-[0.5px] data-checked:inset-ring-components-option-card-option-selected-border',
)}
>
<span className="flex size-6 shrink-0 items-center justify-center rounded-md border border-divider-subtle bg-background-default-subtle">

View File

@ -174,7 +174,7 @@ function AccessControlItem({ type, children }: PropsWithChildren<{
'cursor-pointer rounded-[10px] border-[0.5px] border-components-option-card-option-border bg-components-option-card-option-bg shadow-xs transition-colors',
'hover:border-components-option-card-option-border-hover hover:bg-components-option-card-option-bg-hover',
'focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden',
'data-checked:border-components-option-card-option-selected-border data-checked:bg-components-option-card-option-selected-bg data-checked:ring-[0.5px] data-checked:ring-components-option-card-option-selected-border data-checked:ring-inset',
'data-checked:border-components-option-card-option-selected-border data-checked:bg-components-option-card-option-selected-bg data-checked:inset-ring-[0.5px] data-checked:inset-ring-components-option-card-option-selected-border',
'data-disabled:cursor-not-allowed data-disabled:opacity-60 data-disabled:hover:border-components-option-card-option-border data-disabled:hover:bg-components-option-card-option-bg',
)}
>

View File

@ -47,7 +47,7 @@ export function PermissionSummaryButton({
aria-label={t('access.permissions.editAriaLabel', { environment: environmentLabel })}
onClick={onClick}
className={cn(
'flex h-9 w-full min-w-0 cursor-pointer items-center gap-x-0.5 rounded-lg bg-components-input-bg-normal py-1 pr-2 pl-2.5 outline-hidden hover:bg-state-base-hover-alt focus-visible:bg-state-base-hover-alt focus-visible:ring-1 focus-visible:ring-components-input-border-active focus-visible:ring-inset',
'flex h-9 w-full min-w-0 cursor-pointer items-center gap-x-0.5 rounded-lg bg-components-input-bg-normal py-1 pr-2 pl-2.5 outline-hidden hover:bg-state-base-hover-alt focus-visible:bg-state-base-hover-alt focus-visible:inset-ring-1 focus-visible:inset-ring-components-input-border-active',
disabled && 'cursor-not-allowed opacity-50 hover:bg-components-input-bg-normal',
)}
>

View File

@ -47,7 +47,7 @@ export const TagSearchContent = ({
<button
type="button"
aria-label={t('operation.clear', { ns: 'common' })}
className="mr-1.5 flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-md text-text-tertiary outline-hidden hover:bg-components-input-bg-hover hover:text-text-secondary focus-visible:bg-components-input-bg-hover focus-visible:text-text-secondary focus-visible:ring-1 focus-visible:ring-components-input-border-active focus-visible:ring-inset"
className="mr-1.5 flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-md text-text-tertiary outline-hidden hover:bg-components-input-bg-hover hover:text-text-secondary focus-visible:bg-components-input-bg-hover focus-visible:text-text-secondary focus-visible:inset-ring-1 focus-visible:inset-ring-components-input-border-active"
onClick={() => onInputValueChange('')}
onPointerDown={event => event.preventDefault()}
>