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')
|
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', () => {
|
it('shows Auto beside the existing permission modes', () => {
|
||||||
render(<PermissionModeSelector value="default" onChange={vi.fn()} />)
|
render(<PermissionModeSelector value="default" onChange={vi.fn()} />)
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,11 @@ type Props = {
|
|||||||
*/
|
*/
|
||||||
trigger?: 'chip' | 'elevatedOnly'
|
trigger?: 'chip' | 'elevatedOnly'
|
||||||
menuPlacement?: 'top' | 'bottom'
|
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 */
|
/** Controlled mode: override current value */
|
||||||
value?: PermissionMode
|
value?: PermissionMode
|
||||||
/** Controlled mode: called on change instead of updating global store */
|
/** Controlled mode: called on change instead of updating global store */
|
||||||
@@ -80,7 +85,7 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const PermissionModeSelector = forwardRef<PermissionModeSelectorHandle, Props>(function PermissionModeSelector(
|
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,
|
handleRef,
|
||||||
) {
|
) {
|
||||||
const t = useTranslation()
|
const t = useTranslation()
|
||||||
@@ -175,6 +180,7 @@ export const PermissionModeSelector = forwardRef<PermissionModeSelectorHandle, P
|
|||||||
const menuPlacementClass = menuPlacement === 'bottom'
|
const menuPlacementClass = menuPlacement === 'bottom'
|
||||||
? 'top-full mt-2'
|
? 'top-full mt-2'
|
||||||
: 'bottom-full mb-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
|
// 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
|
// sheet branch and the desktop branch, so `aria-controls` pointed at whichever
|
||||||
// duplicate the browser resolved first.
|
// duplicate the browser resolved first.
|
||||||
@@ -350,7 +356,7 @@ export const PermissionModeSelector = forwardRef<PermissionModeSelectorHandle, P
|
|||||||
</div>
|
</div>
|
||||||
</MobileBottomSheet>
|
</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}>
|
<div className={COMPOSER_MENU_SECTION}>
|
||||||
{t('permMode.executionPermissions')}
|
{t('permMode.executionPermissions')}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -952,6 +952,7 @@ export function GeneralSettings() {
|
|||||||
onChange={(mode) => void setPermissionMode(mode)}
|
onChange={(mode) => void setPermissionMode(mode)}
|
||||||
workDir={t('settings.general.defaultPermissionScope')}
|
workDir={t('settings.general.defaultPermissionScope')}
|
||||||
menuPlacement="bottom"
|
menuPlacement="bottom"
|
||||||
|
menuAlign="right"
|
||||||
/>
|
/>
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
<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