feat(frontend): add resizable split panel layout to LTT page (sub-phase 2.4)
Replace fixed CSS Grid with react-resizable-panels v4 (Group/Panel/Separator). Upper panel (video + query) defaults to 30%, lower panel (response) to 70%. Draggable divider with hover/active state via data-separator attributes. Add ResizeObserver and DOMRect polyfills to test setup for jsdom compatibility. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
parent
f62dcad630
commit
55eee6b98b
|
|
@ -15,6 +15,7 @@
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
|
"react-resizable-panels": "^4.10.0",
|
||||||
"react-router-dom": "^7.14.2",
|
"react-router-dom": "^7.14.2",
|
||||||
"tailwindcss": "^3.4.0"
|
"tailwindcss": "^3.4.0"
|
||||||
},
|
},
|
||||||
|
|
@ -4959,6 +4960,16 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-resizable-panels": {
|
||||||
|
"version": "4.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-4.10.0.tgz",
|
||||||
|
"integrity": "sha512-frjewRQt7TCv/vCH1pJfjZ7RxAhr5pKuqVQtVgzFq/vherxBFOWyC3xMbryx5Ti2wylViGUFc93Etg4rB3E0UA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-router": {
|
"node_modules/react-router": {
|
||||||
"version": "7.14.2",
|
"version": "7.14.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
|
"react-resizable-panels": "^4.10.0",
|
||||||
"react-router-dom": "^7.14.2",
|
"react-router-dom": "^7.14.2",
|
||||||
"tailwindcss": "^3.4.0"
|
"tailwindcss": "^3.4.0"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Film } from 'lucide-react'
|
import { Film } from 'lucide-react'
|
||||||
|
import { Group, Panel, Separator } from 'react-resizable-panels'
|
||||||
import { useQueryDocument } from '../lib/queries'
|
import { useQueryDocument } from '../lib/queries'
|
||||||
import { QueryInput } from '../components/QueryInput'
|
import { QueryInput } from '../components/QueryInput'
|
||||||
import { ExtractedQuestionsDisplay } from '../components/ExtractedQuestionsDisplay'
|
import { ExtractedQuestionsDisplay } from '../components/ExtractedQuestionsDisplay'
|
||||||
|
|
@ -24,22 +25,35 @@ export const LTTPage: React.FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full grid grid-rows-[30%_1fr] grid-cols-2 bg-gray-50">
|
<div className="h-full bg-gray-50">
|
||||||
<div className="border-r border-b border-gray-200 p-4 min-h-0 overflow-hidden">
|
<Group orientation="vertical" id="ltt-main-group">
|
||||||
<VideoPlaceholder />
|
<Panel id="ltt-upper-panel" defaultSize={30} minSize={15} maxSize={60}>
|
||||||
</div>
|
<div className="h-full grid grid-cols-2">
|
||||||
<div className="border-b border-gray-200 p-6 flex flex-col gap-4 overflow-y-auto min-h-0">
|
<div className="border-r border-gray-200 p-4 min-h-0 overflow-hidden">
|
||||||
<QueryInput onSubmit={handleQuerySubmit} isLoading={queryMutation.isPending} />
|
<VideoPlaceholder />
|
||||||
<ExtractedQuestionsDisplay extractedQuestions={queryMutation.data?.extracted_questions} isLoading={queryMutation.isPending} />
|
</div>
|
||||||
</div>
|
<div className="p-6 flex flex-col gap-4 overflow-y-auto min-h-0">
|
||||||
<div className="col-span-2 p-6 border-t border-gray-200 overflow-y-auto min-h-0">
|
<QueryInput onSubmit={handleQuerySubmit} isLoading={queryMutation.isPending} />
|
||||||
<ResponsePanel
|
<ExtractedQuestionsDisplay extractedQuestions={queryMutation.data?.extracted_questions} isLoading={queryMutation.isPending} />
|
||||||
answer={queryMutation.data?.answer ?? null}
|
</div>
|
||||||
sources={queryMutation.data?.sources ?? []}
|
</div>
|
||||||
isLoading={queryMutation.isPending}
|
</Panel>
|
||||||
error={queryMutation.isError ? (queryMutation.error instanceof Error ? queryMutation.error.message : 'Query failed') : null}
|
|
||||||
/>
|
<Separator className="h-2 cursor-row-resize flex items-center justify-center bg-gray-200 [&[data-separator='hover']]:bg-blue-300 [&[data-separator='active']]:bg-blue-400 transition-colors">
|
||||||
</div>
|
<div className="w-8 h-1 rounded-full bg-gray-400 [&[data-separator='hover']_&]:bg-blue-500 [&[data-separator='active']_&]:bg-blue-600 transition-colors" />
|
||||||
|
</Separator>
|
||||||
|
|
||||||
|
<Panel id="ltt-lower-panel" minSize={20}>
|
||||||
|
<div className="h-full p-6 border-t border-gray-200 overflow-y-auto">
|
||||||
|
<ResponsePanel
|
||||||
|
answer={queryMutation.data?.answer ?? null}
|
||||||
|
sources={queryMutation.data?.sources ?? []}
|
||||||
|
isLoading={queryMutation.isPending}
|
||||||
|
error={queryMutation.isError ? (queryMutation.error instanceof Error ? queryMutation.error.message : 'Query failed') : null}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Panel>
|
||||||
|
</Group>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1,12 @@
|
||||||
import '@testing-library/jest-dom'
|
import '@testing-library/jest-dom'
|
||||||
|
|
||||||
|
global.ResizeObserver = class ResizeObserver {
|
||||||
|
observe() {}
|
||||||
|
unobserve() {}
|
||||||
|
disconnect() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
global.DOMRect = class DOMRect {
|
||||||
|
x = 0; y = 0; width = 0; height = 0; top = 0; right = 0; bottom = 0; left = 0
|
||||||
|
static fromRect() { return new DOMRect() }
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue