mirror of
https://github.com/stablyai/orca
synced 2026-04-21 14:17:16 +00:00
The right sidebar drag was hard-capped at 500px, so long file names (e.g. construction drawing sheets, multi-part document names) always truncated with no way to reveal them. The cap is now window-relative — users can drag the sidebar up to (window width − 320px reserve) so the left sidebar and editor can still breathe. Persistence clamp split so the right sidebar keeps a loose 4000px safety net for corrupted values, while the left sidebar stays at 500px.
This commit is contained in:
parent
eced42167f
commit
6bf6a4bda1
3 changed files with 71 additions and 10 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useMemo } from 'react'
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { Files, Search, GitBranch, ListChecks, PanelRight } from 'lucide-react'
|
||||
import { useAppStore } from '@/store'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
|
@ -21,7 +21,13 @@ import SearchPanel from './Search'
|
|||
import ChecksPanel from './ChecksPanel'
|
||||
|
||||
const MIN_WIDTH = 220
|
||||
const MAX_WIDTH = 500
|
||||
// Why: long file names (e.g. construction drawing sheets, multi-part document
|
||||
// names) used to be truncated at a hard 500px cap that no drag could exceed.
|
||||
// We now let the user drag up to nearly the full window width and only keep a
|
||||
// small reserve so the rest of the app (left sidebar, editor) is not squeezed
|
||||
// to zero — the practical ceiling still scales with the user's window size.
|
||||
const MIN_NON_SIDEBAR_AREA = 320
|
||||
const ABSOLUTE_FALLBACK_MAX_WIDTH = 2000
|
||||
|
||||
const ACTIVITY_BAR_SIDE_WIDTH = 40
|
||||
|
||||
|
|
@ -138,11 +144,12 @@ function RightSidebarInner(): React.JSX.Element {
|
|||
: visibleItems[0].id
|
||||
|
||||
const activityBarSideWidth = activityBarPosition === 'side' ? ACTIVITY_BAR_SIDE_WIDTH : 0
|
||||
const maxWidth = useWindowAwareMaxWidth()
|
||||
const { containerRef, onResizeStart } = useSidebarResize<HTMLDivElement>({
|
||||
isOpen: rightSidebarOpen,
|
||||
width: rightSidebarWidth,
|
||||
minWidth: MIN_WIDTH,
|
||||
maxWidth: MAX_WIDTH,
|
||||
maxWidth,
|
||||
deltaSign: -1,
|
||||
renderedExtraWidth: activityBarSideWidth,
|
||||
setWidth: setRightSidebarWidth
|
||||
|
|
@ -268,6 +275,30 @@ function RightSidebarInner(): React.JSX.Element {
|
|||
const RightSidebar = React.memo(RightSidebarInner)
|
||||
export default RightSidebar
|
||||
|
||||
// Why: the drag-resize max is a function of window width, not a constant, so
|
||||
// users with wide displays can expand the sidebar far enough to read long file
|
||||
// names. Falls back to a large constant in non-DOM environments (tests).
|
||||
function useWindowAwareMaxWidth(): number {
|
||||
const [max, setMax] = useState(() => computeMaxRightSidebarWidth())
|
||||
|
||||
useEffect(() => {
|
||||
function update(): void {
|
||||
setMax(computeMaxRightSidebarWidth())
|
||||
}
|
||||
window.addEventListener('resize', update)
|
||||
return () => window.removeEventListener('resize', update)
|
||||
}, [])
|
||||
|
||||
return max
|
||||
}
|
||||
|
||||
function computeMaxRightSidebarWidth(): number {
|
||||
if (typeof window === 'undefined' || !Number.isFinite(window.innerWidth)) {
|
||||
return ABSOLUTE_FALLBACK_MAX_WIDTH
|
||||
}
|
||||
return Math.max(MIN_WIDTH, window.innerWidth - MIN_NON_SIDEBAR_AREA)
|
||||
}
|
||||
|
||||
// ─── Status indicator dot color mapping ──────
|
||||
const STATUS_DOT_COLOR: Record<CheckStatus, string> = {
|
||||
success: 'bg-emerald-500',
|
||||
|
|
|
|||
|
|
@ -42,12 +42,28 @@ describe('createUISlice hydratePersistedUI', () => {
|
|||
store.getState().hydratePersistedUI(
|
||||
makePersistedUI({
|
||||
sidebarWidth: 100,
|
||||
rightSidebarWidth: 900
|
||||
rightSidebarWidth: 100
|
||||
})
|
||||
)
|
||||
|
||||
expect(store.getState().sidebarWidth).toBe(220)
|
||||
expect(store.getState().rightSidebarWidth).toBe(500)
|
||||
expect(store.getState().rightSidebarWidth).toBe(220)
|
||||
})
|
||||
|
||||
it('preserves right sidebar widths above the former 500px cap', () => {
|
||||
const store = createUIStore()
|
||||
|
||||
store.getState().hydratePersistedUI(
|
||||
makePersistedUI({
|
||||
sidebarWidth: 260,
|
||||
rightSidebarWidth: 900
|
||||
})
|
||||
)
|
||||
|
||||
// Left sidebar stays capped; right sidebar now allows wide drag targets
|
||||
// so long file names remain readable.
|
||||
expect(store.getState().sidebarWidth).toBe(260)
|
||||
expect(store.getState().rightSidebarWidth).toBe(900)
|
||||
})
|
||||
|
||||
it('falls back to existing sidebar widths when persisted values are not finite', () => {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable max-lines */
|
||||
import type { StateCreator } from 'zustand'
|
||||
import type { AppState } from '../types'
|
||||
import type {
|
||||
|
|
@ -32,13 +33,18 @@ import {
|
|||
} from '../../../../shared/constants'
|
||||
|
||||
const MIN_SIDEBAR_WIDTH = 220
|
||||
const MAX_SIDEBAR_WIDTH = 500
|
||||
const MAX_LEFT_SIDEBAR_WIDTH = 500
|
||||
// Why: the right sidebar drag-resize is window-relative (see right-sidebar
|
||||
// component), so persisted widths can legitimately be well above the old 500px
|
||||
// cap on wide displays. Use a large hard ceiling purely as a safety net for
|
||||
// corrupted/manually-edited values rather than as a product limit.
|
||||
const MAX_RIGHT_SIDEBAR_WIDTH = 4000
|
||||
|
||||
function sanitizePersistedSidebarWidth(width: unknown, fallback: number): number {
|
||||
function sanitizePersistedSidebarWidth(width: unknown, fallback: number, maxWidth: number): number {
|
||||
if (typeof width !== 'number' || !Number.isFinite(width)) {
|
||||
return fallback
|
||||
}
|
||||
return Math.min(MAX_SIDEBAR_WIDTH, Math.max(MIN_SIDEBAR_WIDTH, width))
|
||||
return Math.min(maxWidth, Math.max(MIN_SIDEBAR_WIDTH, width))
|
||||
}
|
||||
|
||||
export type UISlice = {
|
||||
|
|
@ -276,8 +282,16 @@ export const createUISlice: StateCreator<AppState, [], [], UISlice> = (set, get)
|
|||
// or manually edited. Clamp widths during hydration so invalid values
|
||||
// cannot push the renderer into broken layouts before the user drags a
|
||||
// sidebar again.
|
||||
sidebarWidth: sanitizePersistedSidebarWidth(ui.sidebarWidth, s.sidebarWidth),
|
||||
rightSidebarWidth: sanitizePersistedSidebarWidth(ui.rightSidebarWidth, s.rightSidebarWidth),
|
||||
sidebarWidth: sanitizePersistedSidebarWidth(
|
||||
ui.sidebarWidth,
|
||||
s.sidebarWidth,
|
||||
MAX_LEFT_SIDEBAR_WIDTH
|
||||
),
|
||||
rightSidebarWidth: sanitizePersistedSidebarWidth(
|
||||
ui.rightSidebarWidth,
|
||||
s.rightSidebarWidth,
|
||||
MAX_RIGHT_SIDEBAR_WIDTH
|
||||
),
|
||||
groupBy: ui.groupBy,
|
||||
sortBy,
|
||||
// Why: "Active only" is part of the user's sidebar working set, not a
|
||||
|
|
|
|||
Loading…
Reference in a new issue