From c528c7fca508ab4ce0843bcbb59d741717cb92c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E7=86=B1?= Date: Sat, 15 Aug 2026 16:48:35 +0800 Subject: [PATCH] fix(ui): align dropdowns correctly in RTL layouts (#7522) --- .../src/views/defined-name/DefinedName.tsx | 2 -- .../formula-bar/__tests__/FormulaBar.spec.tsx | 19 ++++++++++++++++-- .../ribbon/TooltipButtonWrapper.tsx | 6 ++++-- .../__tests__/TooltipButtonWrapper.spec.tsx | 20 +++++++++++++++++++ 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/packages/sheets-ui/src/views/defined-name/DefinedName.tsx b/packages/sheets-ui/src/views/defined-name/DefinedName.tsx index 67f76cdd6e..04f7d56c1b 100644 --- a/packages/sheets-ui/src/views/defined-name/DefinedName.tsx +++ b/packages/sheets-ui/src/views/defined-name/DefinedName.tsx @@ -295,7 +295,6 @@ export function DefinedName({ disable }: { disable: boolean }) { univer-box-border univer-size-full univer-appearance-none univer-bg-transparent univer-pl-1.5 univer-pr-5 univer-text-gray-900 focus:univer-outline-none - rtl:univer-pl-5 rtl:univer-pr-1.5 dark:!univer-border-r-gray-700 dark:!univer-text-gray-0 `, borderRightClassName, { 'univer-cursor-not-allowed': disable, @@ -323,7 +322,6 @@ export function DefinedName({ disable }: { disable: boolean }) { univer-absolute univer-right-0 univer-top-0 univer-flex univer-h-full univer-cursor-pointer univer-items-center univer-justify-center univer-px-1 univer-transition-colors univer-duration-200 hover:univer-bg-gray-100 - rtl:univer-left-0 rtl:univer-right-auto dark:!univer-text-gray-0 dark:hover:!univer-bg-gray-800 `, { diff --git a/packages/sheets-ui/src/views/formula-bar/__tests__/FormulaBar.spec.tsx b/packages/sheets-ui/src/views/formula-bar/__tests__/FormulaBar.spec.tsx index f8c916247a..7aad86785f 100644 --- a/packages/sheets-ui/src/views/formula-bar/__tests__/FormulaBar.spec.tsx +++ b/packages/sheets-ui/src/views/formula-bar/__tests__/FormulaBar.spec.tsx @@ -328,11 +328,26 @@ describe('FormulaBar', () => { it('keeps its toolbar layout LTR when the sheet host is RTL', () => { currentBed = createFormulaBarTestBed(); - const rendered = renderWithDependencies(, currentBed.injector); + const rendered = renderWithDependencies( +
+ +
, + currentBed.injector + ); root = rendered.root; container = rendered.container; - expect(rendered.container.querySelector('[data-u-comp="formula-bar"]')?.getAttribute('dir')).toBe('ltr'); + const formulaBar = rendered.container.querySelector('[data-u-comp="formula-bar"]'); + const definedName = formulaBar?.querySelector('[data-u-comp="defined-name"]'); + const input = definedName?.querySelector('input'); + const dropdownTrigger = definedName?.querySelector('a'); + + expect(formulaBar?.getAttribute('dir')).toBe('ltr'); + expect(input?.classList).toContain('univer-pl-1.5'); + expect(input?.classList).toContain('univer-pr-5'); + expect(input?.className).not.toContain('rtl:'); + expect(dropdownTrigger?.classList).toContain('univer-right-0'); + expect(dropdownTrigger?.className).not.toContain('rtl:'); }); it('closes editing through the command service when cancel is clicked', async () => { diff --git a/packages/ui/src/views/components/ribbon/TooltipButtonWrapper.tsx b/packages/ui/src/views/components/ribbon/TooltipButtonWrapper.tsx index 36c5339b24..4da502cb41 100644 --- a/packages/ui/src/views/components/ribbon/TooltipButtonWrapper.tsx +++ b/packages/ui/src/views/components/ribbon/TooltipButtonWrapper.tsx @@ -19,6 +19,7 @@ import type { ReactNode } from 'react'; import type { IMenuItem, IValueOption } from '../../../services/menu/menu'; import { clsx, + ConfigContext, Dropdown, DropdownMenu, Tooltip, @@ -149,7 +150,8 @@ export const TooltipWrapper = forwardRef, 'overlay'> & { overlay: ReactNode; align?: 'start' | 'end' | 'center' }) { - const { children, overlay, disabled, align = 'start' } = props; + const { children, overlay, disabled, align } = props; + const { direction } = useContext(ConfigContext); const { dropdownVisible, setDropdownVisible } = useContext(TooltipWrapperContext); const triggerRef = useRef(null); const overlayRef = useRef(null); @@ -181,7 +183,7 @@ export function DropdownWrapper(props: Omit, 'overlay'> return ( {overlay} diff --git a/packages/ui/src/views/components/ribbon/__tests__/TooltipButtonWrapper.spec.tsx b/packages/ui/src/views/components/ribbon/__tests__/TooltipButtonWrapper.spec.tsx index 2971624a29..126e19791d 100644 --- a/packages/ui/src/views/components/ribbon/__tests__/TooltipButtonWrapper.spec.tsx +++ b/packages/ui/src/views/components/ribbon/__tests__/TooltipButtonWrapper.spec.tsx @@ -21,6 +21,7 @@ import type { ComponentType, ReactElement } from 'react'; import { cleanup, fireEvent, render } from '@testing-library/react'; import { ILogService, Injector, LocaleService } from '@univerjs/core'; +import { ConfigProvider } from '@univerjs/design'; import { of } from 'rxjs'; import { afterEach, describe, expect, it, vi } from 'vitest'; @@ -134,6 +135,25 @@ describe('DropdownMenuLabel', () => { }); describe('DropdownWrapper', () => { + it('opens toward the left in RTL layouts', async () => { + const { findByText, getByRole } = render( + + + + Dropdown content}> + + + + + + ); + + fireEvent.click(getByRole('button', { name: 'Open dropdown' })); + const content = (await findByText('Dropdown content')).closest('[data-slot="popover-content"]'); + + expect(content?.getAttribute('data-align')).toBe('end'); + }); + it('closes on an outside pointerdown even when no click follows', async () => { const { findByText, getByRole, queryByText } = render(