Files
Rpa_Skills/rpa-dashboard-ai/templates/reference/web/components/SplitPane.tsx
T
VasanthandClaude Opus 5.5 8951ac05a5 AI skill: SKILL.md, AI contract, scrubbed references, eval fixture (d)
- rpa-dashboard-ai/SKILL.md: trigger description and the shared question flow;
  points at the no-AI frontend.md, themes and palette instead of copying them.
- templates/contract.md: runs, review rows, endpoints and the server approve gate.
- Superbill extracts: patient names and the client name removed, every file
  carries an EXAMPLE ONLY banner. tests/test_reference_scan.py checks both skills
  for patient data, internal IPs, credentials and keys.
- tests/fixtures/d-ai-extraction and its eval entry, which runs page_check_ai
  on the built folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 15:13:55 +05:30

148 lines
5.4 KiB
TypeScript

/* EXAMPLE ONLY: an extract from the Superbill Review prototype, kept as reference for porting. ECW/eClinicalWorks, Tebra, 'Example Client', SPB_* tables and patient/ticket wording are that project's examples. Don't copy them into a new project. */
import { useCallback, useEffect, useRef, useState } from 'react'
const KEY = 'superbill.split'
/**
* Draggable two-pane split, PDF on the left and values on the right.
*
* The ratio is remembered: how much page a reviewer needs beside the values
* depends on their screen and their document, and re-dragging it every session
* would be a small tax paid hundreds of times.
*
* The drag listeners live on window, not the handle, so the pointer can outrun
* the 6px handle mid-drag without dropping it.
*
* Below `md`, side-by-side stops being usable at all — squeezing both panes
* into a 375px screen leaves the PDF at ~165px wide, unreadable. Below that
* breakpoint this becomes two full-width tabs instead of a shrinking split;
* both panes stay mounted (toggling `hidden`/`flex`, not unmounting) so
* switching tabs never resets the PDF viewer's scroll or zoom.
*/
export function SplitPane({
left,
right,
min = 25,
max = 75,
ratio,
}: {
left: React.ReactNode
right: React.ReactNode
min?: number
max?: number
/**
* Force a split for as long as it is set, without overwriting the reviewer's
* remembered one — fullscreen widens the page and docks the values to the
* right, and leaving it must restore the ratio they chose, not this one.
*/
ratio?: number
}) {
const [saved, setSaved] = useState(() => {
const stored = Number(localStorage.getItem(KEY))
// The scan is the thing being read; the values are short. The page gets
// the larger share by default.
return stored >= min && stored <= max ? stored : 60
})
const pct = ratio ?? saved
const setPct = setSaved
const [dragging, setDragging] = useState(false)
const [mobileTab, setMobileTab] = useState<'left' | 'right'>('left')
const box = useRef<HTMLDivElement>(null)
useEffect(() => {
localStorage.setItem(KEY, String(saved))
}, [saved])
const onMove = useCallback(
(e: MouseEvent) => {
const rect = box.current?.getBoundingClientRect()
if (!rect) return
const next = ((e.clientX - rect.left) / rect.width) * 100
setPct(Math.min(max, Math.max(min, next)))
},
[min, max],
)
useEffect(() => {
if (!dragging) return
const stop = () => setDragging(false)
window.addEventListener('mousemove', onMove)
window.addEventListener('mouseup', stop)
// The PDF pane has its own drag-to-pan handler; without this it would
// interpret the divider drag as a pan and text would select across both
// panes on the way.
document.body.style.userSelect = 'none'
document.body.style.cursor = 'col-resize'
return () => {
window.removeEventListener('mousemove', onMove)
window.removeEventListener('mouseup', stop)
document.body.style.userSelect = ''
document.body.style.cursor = ''
}
}, [dragging, onMove])
return (
<div className="flex min-h-0 flex-1 flex-col">
{/* Tab bar: mobile only. The drag handle is the equivalent affordance on
desktop, so this disappears at md instead of coexisting with it. */}
<div className="flex shrink-0 border-b border-line-soft bg-rail md:hidden">
<button
onClick={() => setMobileTab('left')}
aria-current={mobileTab === 'left'}
className="flex-1 border-b-2 py-2.5 font-sora text-[13px] font-semibold transition-colors"
style={{
borderColor: mobileTab === 'left' ? 'var(--accent-1)' : 'transparent',
color: mobileTab === 'left' ? 'var(--ink)' : 'var(--muted)',
}}
>
Source page
</button>
<button
onClick={() => setMobileTab('right')}
aria-current={mobileTab === 'right'}
className="flex-1 border-b-2 py-2.5 font-sora text-[13px] font-semibold transition-colors"
style={{
borderColor: mobileTab === 'right' ? 'var(--accent-1)' : 'transparent',
color: mobileTab === 'right' ? 'var(--ink)' : 'var(--muted)',
}}
>
Fields to review
</button>
</div>
<div ref={box} className="flex min-h-0 flex-1 flex-col md:flex-row">
<div
style={{ '--split-w': `${pct}%` } as React.CSSProperties}
className={`min-h-0 min-w-0 flex-1 border-b border-line md:w-[var(--split-w)] md:flex-none md:border-b-0 md:border-r md:border-line ${
mobileTab === 'left' ? 'flex' : 'hidden'
} md:flex`}
>
{left}
</div>
<div
onMouseDown={() => setDragging(true)}
onDoubleClick={() => setPct(52)}
title="Drag to resize — double-click to reset"
className={`hidden w-1.5 shrink-0 cursor-col-resize transition-colors md:block ${
dragging ? 'bg-accent-1' : 'bg-line hover:bg-muted'
}`}
/>
{/* Pointer events off during the drag, so neither pane steals the move. */}
<div
style={{
'--split-w': `${100 - pct}%`,
pointerEvents: dragging ? 'none' : undefined,
} as React.CSSProperties}
className={`min-h-0 min-w-0 flex-1 md:w-[var(--split-w)] md:flex-none ${
mobileTab === 'right' ? 'flex' : 'hidden'
} md:flex`}
>
{right}
</div>
</div>
</div>
)
}