mirror of
https://github.com/NanmiCoder/claude-code-haha.git
synced 2026-10-10 03:43:11 +08:00
fix(desktop): keep the settings permission menu inside the window
The default-permission menu in Settings > General grew rightwards from the trigger's left edge. The trigger sits at the end of the row, so the 340px menu ran past the content column and was clipped. Add a menuAlign option and right-align the menu there; composer callers keep the left alignment. Fixes #1474
This commit is contained in:
@@ -479,6 +479,26 @@ describe('PermissionModeSelector', () => {
|
||||
expect(onChange).toHaveBeenCalledWith('acceptEdits')
|
||||
})
|
||||
|
||||
it('opens the desktop menu from the trigger start edge by default', () => {
|
||||
render(<PermissionModeSelector value="default" onChange={vi.fn()} />)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Ask permissions' }))
|
||||
|
||||
const menu = screen.getByRole('menu')
|
||||
expect(menu).toHaveClass('left-0')
|
||||
expect(menu).not.toHaveClass('right-0')
|
||||
})
|
||||
|
||||
it('anchors the desktop menu to the trigger end edge when asked to align right', () => {
|
||||
render(<PermissionModeSelector value="default" onChange={vi.fn()} menuAlign="right" />)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Ask permissions' }))
|
||||
|
||||
const menu = screen.getByRole('menu')
|
||||
expect(menu).toHaveClass('right-0')
|
||||
expect(menu).not.toHaveClass('left-0')
|
||||
})
|
||||
|
||||
it('shows Auto beside the existing permission modes', () => {
|
||||
render(<PermissionModeSelector value="default" onChange={vi.fn()} />)
|
||||
|
||||
|
||||
@@ -73,6 +73,11 @@ type Props = {
|
||||
*/
|
||||
trigger?: 'chip' | 'elevatedOnly'
|
||||
menuPlacement?: 'top' | 'bottom'
|
||||
/**
|
||||
* Which trigger edge the desktop menu lines up with. `right` keeps it inside
|
||||
* the container when the trigger sits at the end of a row, as in Settings.
|
||||
*/
|
||||
menuAlign?: 'left' | 'right'
|
||||
/** Controlled mode: override current value */
|
||||
value?: PermissionMode
|
||||
/** Controlled mode: called on change instead of updating global store */
|
||||
@@ -80,7 +85,7 @@ type Props = {
|
||||
}
|
||||
|
||||
export const PermissionModeSelector = forwardRef<PermissionModeSelectorHandle, Props>(function PermissionModeSelector(
|
||||
{ sessionId, workDir: workDirProp, compact = false, trigger = 'chip', menuPlacement = 'top', value, onChange }: Props = {},
|
||||
{ sessionId, workDir: workDirProp, compact = false, trigger = 'chip', menuPlacement = 'top', menuAlign = 'left', value, onChange }: Props = {},
|
||||
handleRef,
|
||||
) {
|
||||
const t = useTranslation()
|
||||
@@ -175,6 +180,7 @@ export const PermissionModeSelector = forwardRef<PermissionModeSelectorHandle, P
|
||||
const menuPlacementClass = menuPlacement === 'bottom'
|
||||
? 'top-full mt-2'
|
||||
: 'bottom-full mb-2'
|
||||
const menuAlignClass = menuAlign === 'right' ? 'right-0' : 'left-0'
|
||||
// Generated, not hard-coded: the previous literal id was rendered by both the
|
||||
// sheet branch and the desktop branch, so `aria-controls` pointed at whichever
|
||||
// duplicate the browser resolved first.
|
||||
@@ -350,7 +356,7 @@ export const PermissionModeSelector = forwardRef<PermissionModeSelectorHandle, P
|
||||
</div>
|
||||
</MobileBottomSheet>
|
||||
) : (
|
||||
<div id={menuId} ref={menuRef} role="menu" className={`absolute left-0 ${menuPlacementClass} z-[var(--z-dropdown)] w-[340px] ${COMPOSER_POPOVER}`}>
|
||||
<div id={menuId} ref={menuRef} role="menu" className={`absolute ${menuAlignClass} ${menuPlacementClass} z-[var(--z-dropdown)] w-[340px] ${COMPOSER_POPOVER}`}>
|
||||
<div className={COMPOSER_MENU_SECTION}>
|
||||
{t('permMode.executionPermissions')}
|
||||
</div>
|
||||
|
||||
@@ -952,6 +952,7 @@ export function GeneralSettings() {
|
||||
onChange={(mode) => void setPermissionMode(mode)}
|
||||
workDir={t('settings.general.defaultPermissionScope')}
|
||||
menuPlacement="bottom"
|
||||
menuAlign="right"
|
||||
/>
|
||||
</SettingsRow>
|
||||
<SettingsRow
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { cleanup, fireEvent, render, screen, within } from '@testing-library/react'
|
||||
import '@testing-library/jest-dom'
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import { useOpenTargetStore } from '@/stores/openTargetStore'
|
||||
import { useSettingsStore } from '@/stores/settingsStore'
|
||||
import { GeneralSettings } from './GeneralSettings'
|
||||
|
||||
describe('General settings default permission menu', () => {
|
||||
beforeEach(() => {
|
||||
useSettingsStore.setState({
|
||||
locale: 'zh',
|
||||
permissionMode: 'default',
|
||||
fetchOutputStyles: async () => {},
|
||||
fetchAppMode: async () => {},
|
||||
})
|
||||
useOpenTargetStore.setState({ ensureTargets: async () => {} })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
useSettingsStore.setState(useSettingsStore.getInitialState(), true)
|
||||
useOpenTargetStore.setState(useOpenTargetStore.getInitialState(), true)
|
||||
})
|
||||
|
||||
// The trigger sits at the end of a settings row, against the right edge of
|
||||
// the scrolling content column. A menu growing rightwards from the trigger's
|
||||
// left edge ran past the window and was clipped (#1474).
|
||||
it('opens the menu leftwards from the trigger at the end of the row', () => {
|
||||
render(<GeneralSettings />)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '询问权限' }))
|
||||
|
||||
const menu = screen.getByRole('menu')
|
||||
expect(within(menu).getByRole('menuitem', { name: /询问权限/ })).toBeInTheDocument()
|
||||
expect(menu).toHaveClass('right-0')
|
||||
expect(menu).not.toHaveClass('left-0')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user