diff --git a/docs/content/docs/guides/unibox.mdx b/docs/content/docs/guides/unibox.mdx index 8e3d1bc45..b7ae50a06 100644 --- a/docs/content/docs/guides/unibox.mdx +++ b/docs/content/docs/guides/unibox.mdx @@ -13,6 +13,7 @@ The columns are yours to size. Every choice here is remembered in the browser yo | Control | What it does | | --- | --- | +| The divider between the scope rail and the list | Drag it to resize the scope rail. With focus, arrow keys nudge it (hold `Shift` for a bigger step), `Home` and `End` set the minimum and maximum, and `Enter` (or a double-click) restores the default 220 px. The width is remembered in this browser. | | The divider between the list and the thread | Drag it to widen the conversation list so long subjects fit, or to give the space back to the thread. It also takes the keyboard once it has focus: arrow keys nudge it (hold `Shift` for a bigger step), `Home` and `End` go to the narrowest and widest the window allows, `Enter` (or a double-click) puts it back to the default. | | **Collapse** at the bottom of the left navigation, or `b` | Shrinks Warmbly's own navigation to an icon rail and gives the width to the page. Row labels become tooltips; unread mail keeps its count badge and an open [Advisor](/guides/advisor/) finding shows as a coloured dot on the icon. The same control expands it again. | | The section headers in the left navigation (Email, CRM, Resources) | Click one to fold its section away, and again to bring it back. Like the other layout choices, it is remembered in this browser. A folded section still shows the page you are on, and a coloured dot beside its header flags an open [Advisor](/guides/advisor/) finding on one of the pages it hides. The icon rail follows the same choice. | diff --git a/web/src/app/app/unibox/page.tsx b/web/src/app/app/unibox/page.tsx index f8f743970..dc737421b 100644 --- a/web/src/app/app/unibox/page.tsx +++ b/web/src/app/app/unibox/page.tsx @@ -28,6 +28,9 @@ import { UNIBOX_LIST_DEFAULT_WIDTH, UNIBOX_LIST_MAX_WIDTH, UNIBOX_LIST_MIN_WIDTH, + UNIBOX_RAIL_DEFAULT_WIDTH, + UNIBOX_RAIL_MAX_WIDTH, + UNIBOX_RAIL_MIN_WIDTH, } from "@/stores"; import { uniboxListMaxWidth, uniboxThreadReserve } from "@/lib/uniboxLayout"; import { useResizablePane } from "@/hooks/useResizablePane"; @@ -66,9 +69,12 @@ export default function UniboxPage() { // width the column does not have. const listWidth = useAppStore((s) => s.uniboxListWidth); const setListWidth = useAppStore((s) => s.setUniboxListWidth); + const railWidth = useAppStore((s) => s.uniboxRailWidth); + const setRailWidth = useAppStore((s) => s.setUniboxRailWidth); const contactRailOpen = useAppStore((s) => s.uniboxContactRailOpen); const isWide = useMediaQuery(LG_QUERY); const rowRef = React.useRef(null); + const railRef = React.useRef(null); const listRef = React.useRef(null); const [maxWidth, setMaxWidth] = React.useState(UNIBOX_LIST_MAX_WIDTH); @@ -109,8 +115,23 @@ export default function UniboxPage() { sync(); const ro = new ResizeObserver(sync); ro.observe(row); + // A rail drag moves the list's left edge without resizing the row. + if (railRef.current) ro.observe(railRef.current); return () => ro.disconnect(); - }, [measureMax]); + }, [measureMax, railWidth]); + + const { width: renderedRailWidth, separatorProps: railSeparatorProps } = useResizablePane({ + value: railWidth, + onChange: setRailWidth, + min: UNIBOX_RAIL_MIN_WIDTH, + max: UNIBOX_RAIL_MAX_WIDTH, + defaultValue: UNIBOX_RAIL_DEFAULT_WIDTH, + paneRef: railRef, + cssVar: "--unibox-rail-w", + label: "Resize the scope rail", + controls: "unibox-scope-rail", + valueText: (w) => `Scope rail ${w} pixels`, + }); // The splitter itself: pointer capture, the body lock, the window-splitter // keys and the ARIA bundle all live in the shared hook, which the assistant @@ -447,10 +468,22 @@ export default function UniboxPage() { />
-