Arrange the rail: drag pages into your own order, kept with the account
The close cross on a detail pane is drawn, not a glyph, and sits in no box. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
b8ea7abcc7
commit
7e3e916f25
+44
-5
@@ -1,4 +1,5 @@
|
||||
import { ReactNode } from 'react'
|
||||
import { ReactNode, useRef, useState } from 'react'
|
||||
import { orderBy, resetRailOrder, setRailOrder, useRailOrder } from './railorder'
|
||||
|
||||
// Every group is a page: clicking it opens it. `soon` groups are inert placeholders
|
||||
// for the roadmap.
|
||||
@@ -6,14 +7,48 @@ export interface Group { id: string; label: string; icon: ReactNode; soon?: bool
|
||||
|
||||
interface Props { groups: Group[]; view: string; header?: ReactNode; badges?: Record<string, number>; onView: (id: string) => void }
|
||||
|
||||
// The pages are in the order the account keeps (railorder.ts). "Arrange" at the foot puts a
|
||||
// handle on each; drag one up or down — pointer events, so a finger works as well as a mouse —
|
||||
// and the order is saved with the UI prefs when it is let go.
|
||||
export default function Rail({ groups, view, header, badges, onView }: Props) {
|
||||
const saved = useRailOrder()
|
||||
const [arranging, setArranging] = useState(false)
|
||||
const [drag, setDrag] = useState<{ id: string; order: string[] } | null>(null)
|
||||
const rows = useRef(new Map<string, HTMLElement>())
|
||||
const ordered = drag ? orderBy(groups, drag.order) : orderBy(groups, saved)
|
||||
|
||||
const start = (id: string) => (e: React.PointerEvent<HTMLElement>) => {
|
||||
e.preventDefault()
|
||||
e.currentTarget.setPointerCapture(e.pointerId)
|
||||
setDrag({ id, order: ordered.map((g) => g.id) })
|
||||
}
|
||||
const move = (e: React.PointerEvent<HTMLElement>) => {
|
||||
if (!drag) return
|
||||
// where the pointer is among the rows: the row whose middle it has crossed
|
||||
const y = e.clientY
|
||||
const order = drag.order.slice()
|
||||
const from = order.indexOf(drag.id)
|
||||
let to = from
|
||||
order.forEach((id, i) => {
|
||||
const el = rows.current.get(id)
|
||||
if (!el || id === drag.id) return
|
||||
const r = el.getBoundingClientRect()
|
||||
const mid = r.top + r.height / 2
|
||||
if (i < from && y < mid && i < to) to = i
|
||||
if (i > from && y > mid && i > to) to = i
|
||||
})
|
||||
if (to !== from) { order.splice(from, 1); order.splice(to, 0, drag.id); setDrag({ id: drag.id, order }) }
|
||||
}
|
||||
const end = () => { if (drag) { setRailOrder(drag.order); setDrag(null) } }
|
||||
|
||||
return (
|
||||
<nav className="rail">
|
||||
<nav className={'rail' + (arranging ? ' arranging' : '')}>
|
||||
{header}
|
||||
{groups.map((g) => (
|
||||
<div key={g.id} className={'group' + (g.soon ? ' soon' : '')}>
|
||||
{ordered.map((g) => (
|
||||
<div key={g.id} ref={(el) => { if (el) rows.current.set(g.id, el); else rows.current.delete(g.id) }} className={'group' + (g.soon ? ' soon' : '') + (drag?.id === g.id ? ' dragging' : '')}>
|
||||
<button className={'group-head' + (view === g.id ? ' active' : '')} disabled={g.soon}
|
||||
onClick={() => { if (!g.soon) onView(g.id) }}>
|
||||
onClick={() => { if (!g.soon && !arranging) onView(g.id) }}>
|
||||
<span className="handle" title="drag to move" onPointerDown={start(g.id)} onPointerMove={move} onPointerUp={end} onPointerCancel={end}>⋮⋮</span>
|
||||
<span className="gicon">{g.icon}</span>
|
||||
<span>{g.label}</span>
|
||||
{g.soon && <span className="badge">soon</span>}
|
||||
@@ -21,6 +56,10 @@ export default function Rail({ groups, view, header, badges, onView }: Props) {
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<div className="rail-foot">
|
||||
<button className={arranging ? 'on' : ''} title="put the pages in your own order" onClick={() => setArranging(!arranging)}>{arranging ? 'Done' : '⇅ Arrange'}</button>
|
||||
{arranging && saved.length > 0 && <button onClick={() => resetRailOrder()}>Reset</button>}
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user