Private
Public Access
Make the room list sidebar resizable
Generalizes useResizableWidth with a left/right anchor option and wires it into the sidebar the same way the room info panel already uses it, so both sides of the chat shell can be dragged to a comfortable width.
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
.sidebar {
|
.sidebar {
|
||||||
width: 300px;
|
position: relative;
|
||||||
min-width: 300px;
|
flex: none;
|
||||||
|
min-width: 220px;
|
||||||
|
max-width: 480px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-right: 1px solid var(--ds-border);
|
border-right: 1px solid var(--ds-border);
|
||||||
@@ -8,6 +10,22 @@
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar-resize-handle {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: -3px;
|
||||||
|
width: 6px;
|
||||||
|
cursor: col-resize;
|
||||||
|
z-index: 5;
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-resize-handle:hover,
|
||||||
|
.sidebar-resize-handle:active {
|
||||||
|
background: color-mix(in srgb, var(--ds-accent) 40%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar-toolbar {
|
.sidebar-toolbar {
|
||||||
padding: var(--sp-3);
|
padding: var(--sp-3);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useResizableWidth } from '../hooks/useResizableWidth'
|
||||||
import type { MyRoomItem } from '../types'
|
import type { MyRoomItem } from '../types'
|
||||||
import { RoomRow } from './RoomRow'
|
import { RoomRow } from './RoomRow'
|
||||||
import './Sidebar.css'
|
import './Sidebar.css'
|
||||||
@@ -24,8 +25,17 @@ export function Sidebar({
|
|||||||
const query = searchQuery.trim().toLowerCase()
|
const query = searchQuery.trim().toLowerCase()
|
||||||
const filtered = query ? rooms.filter((r) => r.name.toLowerCase().includes(query)) : rooms
|
const filtered = query ? rooms.filter((r) => r.name.toLowerCase().includes(query)) : rooms
|
||||||
|
|
||||||
|
const { width, startResize } = useResizableWidth({
|
||||||
|
storageKey: 'sidebar-width',
|
||||||
|
defaultWidth: 300,
|
||||||
|
min: 220,
|
||||||
|
max: 480,
|
||||||
|
anchor: 'left',
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="sidebar">
|
<aside className="sidebar" style={{ width }}>
|
||||||
|
<div className="sidebar-resize-handle" onPointerDown={startResize} />
|
||||||
<div className="sidebar-toolbar">
|
<div className="sidebar-toolbar">
|
||||||
<div className="sidebar-search">
|
<div className="sidebar-search">
|
||||||
<svg width="14" height="14" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
<svg width="14" height="14" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
||||||
|
|||||||
@@ -5,12 +5,22 @@ interface UseResizableWidthOptions {
|
|||||||
defaultWidth: number
|
defaultWidth: number
|
||||||
min: number
|
min: number
|
||||||
max: number
|
max: number
|
||||||
|
// 'right' (default): panel sits against the viewport's right edge, width
|
||||||
|
// is measured from the cursor to that edge -- a handle on the panel's
|
||||||
|
// LEFT edge drags naturally. 'left': panel sits against the left edge,
|
||||||
|
// width is just the cursor's x position -- a handle on the panel's RIGHT
|
||||||
|
// edge drags naturally.
|
||||||
|
anchor?: 'left' | 'right'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Right-anchored resizable panel: width is the distance from the cursor to
|
// Persists to localStorage so it survives a reload.
|
||||||
// the viewport's right edge, so a handle on the panel's left edge drags
|
export function useResizableWidth({
|
||||||
// naturally. Persists to localStorage so it survives a reload.
|
storageKey,
|
||||||
export function useResizableWidth({ storageKey, defaultWidth, min, max }: UseResizableWidthOptions) {
|
defaultWidth,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
anchor = 'right',
|
||||||
|
}: UseResizableWidthOptions) {
|
||||||
const [width, setWidth] = useState(() => {
|
const [width, setWidth] = useState(() => {
|
||||||
const stored = Number(localStorage.getItem(storageKey))
|
const stored = Number(localStorage.getItem(storageKey))
|
||||||
return stored >= min && stored <= max ? stored : defaultWidth
|
return stored >= min && stored <= max ? stored : defaultWidth
|
||||||
@@ -22,7 +32,8 @@ export function useResizableWidth({ storageKey, defaultWidth, min, max }: UseRes
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleMove(e: PointerEvent<Window> | globalThis.PointerEvent) {
|
function handleMove(e: PointerEvent<Window> | globalThis.PointerEvent) {
|
||||||
if (!draggingRef.current) return
|
if (!draggingRef.current) return
|
||||||
const next = Math.min(max, Math.max(min, window.innerWidth - e.clientX))
|
const raw = anchor === 'left' ? e.clientX : window.innerWidth - e.clientX
|
||||||
|
const next = Math.min(max, Math.max(min, raw))
|
||||||
setWidth(next)
|
setWidth(next)
|
||||||
}
|
}
|
||||||
function handleUp() {
|
function handleUp() {
|
||||||
|
|||||||
Reference in New Issue
Block a user