Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions App/frontend-app/src/components/resizer/panelResizer.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
.panel-resizer {
width: 8px;
background: #f0f0f0;
border-left: 1px solid #ddd;
border-right: 1px solid #ddd;
cursor: col-resize;
position: relative;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.2s ease;
user-select: none;
flex-shrink: 0;

&:hover {
background: #e0e0e0;
}

&.dragging {
background: #d0d0d0;
cursor: col-resize;
}
}

.resizer-handle {
height: 60px;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
}

.resizer-dots {
display: flex;
flex-direction: column;
gap: 3px;
align-items: center;
}

.dot {
width: 3px;
height: 3px;
background-color: #999;
border-radius: 50%;
}

.panel-resizer:hover .dot {
background-color: #666;
}

.panel-resizer.dragging .dot {
background-color: #333;
}

/* Global cursor override when dragging */
body.resizing {
cursor: col-resize !important;
user-select: none !important;
}

body.resizing * {
pointer-events: none !important;
}
125 changes: 125 additions & 0 deletions App/frontend-app/src/components/resizer/panelResizer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import React, { useState, useRef, useEffect } from 'react';

interface PanelResizerProps {
onResize?: (leftWidth: number, rightWidth: number) => void;
minLeftWidth?: number;
minRightWidth?: number;
initialLeftWidth?: number;
}

export const PanelResizer: React.FC<PanelResizerProps> = ({
onResize,
minLeftWidth = 200,
minRightWidth = 200,
initialLeftWidth = 50
}) => {
const [isDragging, setIsDragging] = useState(false);
const resizerRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
if (!isDragging || !resizerRef.current) return;

const container = resizerRef.current.parentElement;
if (!container) return;

const containerRect = container.getBoundingClientRect();
const newLeftWidth = ((e.clientX - containerRect.left) / containerRect.width) * 100;
const newRightWidth = 100 - newLeftWidth;

// Apply minimum width constraints
const minLeftPercent = (minLeftWidth / containerRect.width) * 100;
const minRightPercent = (minRightWidth / containerRect.width) * 100;

if (newLeftWidth >= minLeftPercent && newRightWidth >= minRightPercent) {
onResize?.(newLeftWidth, newRightWidth);
}
};

const handleMouseUp = () => {
setIsDragging(false);
document.body.style.cursor = '';
document.body.style.userSelect = '';
};

if (isDragging) {
document.body.style.cursor = 'col-resize';
document.body.style.userSelect = 'none';
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}

return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}, [isDragging, onResize, minLeftWidth, minRightWidth]);

const handleMouseDown = () => {
setIsDragging(true);
};

const resizerStyle: React.CSSProperties = {
width: '8px',
background: isDragging ? '#d0d0d0' : '#f0f0f0',
borderLeft: '1px solid #ddd',
borderRight: '1px solid #ddd',
cursor: 'col-resize',
position: 'relative',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transition: 'background-color 0.2s ease',
userSelect: 'none',
flexShrink: 0,
};

const handleStyle: React.CSSProperties = {
height: '60px',
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
pointerEvents: 'none',
};

const dotsStyle: React.CSSProperties = {
display: 'flex',
flexDirection: 'column',
gap: '3px',
alignItems: 'center',
};

const dotStyle: React.CSSProperties = {
width: '3px',
height: '3px',
backgroundColor: isDragging ? '#333' : '#999',
borderRadius: '50%',
};

return (
<div
ref={resizerRef}
style={resizerStyle}
onMouseDown={handleMouseDown}
onMouseEnter={(e) => {
if (!isDragging) {
(e.target as HTMLElement).style.background = '#e0e0e0';
}
}}
onMouseLeave={(e) => {
if (!isDragging) {
(e.target as HTMLElement).style.background = '#f0f0f0';
}
}}
>
<div style={handleStyle}>
<div style={dotsStyle}>
<div style={dotStyle}></div>
<div style={dotStyle}></div>
<div style={dotStyle}></div>
</div>
</div>
</div>
);
};
48 changes: 48 additions & 0 deletions App/frontend-app/src/pages/home/home.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,64 @@
.filters-panel {
width: 15% !important;
min-width: 229px;
transition: width 0.3s ease-in-out;
}

.documents-panel {
width: 34% !important;
min-width: 410px;
transition: width 0.3s ease-in-out;
}

.chat-panel {
width: 51% !important;
min-width: 300px;
transition: width 0.3s ease-in-out;
}

.filters-panel-collapsed {
width: 50px !important;
min-width: 50px;
transition: width 0.3s ease-in-out;
}
}

/* Resizable panels styling */
.resizable-container {
display: flex;
flex-direction: row;
flex-grow: 1;
overflow: hidden;
}

.resizable-panel {
display: flex;
flex-direction: column;
min-width: 200px;
overflow: hidden;
}

/* Filter panel transitions */
.filter-panel-enter {
opacity: 0;
transform: translateX(-100%);
}

.filter-panel-enter-active {
opacity: 1;
transform: translateX(0);
transition: opacity 300ms, transform 300ms;
}

.filter-panel-exit {
opacity: 1;
transform: translateX(0);
}

.filter-panel-exit-active {
opacity: 0;
transform: translateX(-100%);
transition: opacity 300ms, transform 300ms;
}

@media (max-width: 1080px) {
Expand Down
86 changes: 68 additions & 18 deletions App/frontend-app/src/pages/home/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import { SidecarCopilot } from "../../components/sidecarCopilot/sidecar";
import homeStyles from "./home.module.scss";
import { ChatRoom } from "../../components/chat/chatRoom";
import UploadFilesButton from "../../components/uploadButton/uploadButton";
import { PanelResizer } from "../../components/resizer/panelResizer";
import { ChevronLeft24Regular, ChevronRight24Regular } from "@fluentui/react-icons";

const useStyles = makeStyles({
dropdown: {
Expand Down Expand Up @@ -64,6 +66,9 @@ export function Home({ isSearchResultsPage }: HomeProps) {
const [isLoading, setIsLoading] = useState<boolean>(false);
const [searchParams, setSearchParams] = useSearchParams();
const [areFiltersVisible, setAreFiltersVisible] = useState(true);
const [isFilterPanelCollapsed, setIsFilterPanelCollapsed] = useState(false);
const [documentsWidth, setDocumentsWidth] = useState(34); // Initial width percentage
const [chatWidth, setChatWidth] = useState(51); // Initial width percentage
const [showCopilot, setShowCopilot] = useState<boolean>(false);
const [incomingFilter, setIncomingFilter] = useState(
"(document/embedded eq false and document/translated eq false)"
Expand Down Expand Up @@ -236,6 +241,15 @@ export function Home({ isSearchResultsPage }: HomeProps) {
setAreFiltersVisible((prevAreFiltersVisible) => !prevAreFiltersVisible);
}

function toggleFilterPanel(): void {
setIsFilterPanelCollapsed(!isFilterPanelCollapsed);
}

const handlePanelResize = (leftWidth: number, rightWidth: number) => {
setDocumentsWidth(leftWidth);
setChatWidth(rightWidth);
};

const handleSortSelected = (sort: string) => {
setInOrderBy(sort);
};
Expand Down Expand Up @@ -526,25 +540,50 @@ export function Home({ isSearchResultsPage }: HomeProps) {

<main className="w-full h-full flex flex-col">
<div className="flex flex-col md:flex-row flex-grow">
{/* Left Section: Filter */}
<div className={`flex flex-col w-full bg-white shadow-md p-4 ${homeStyles["no-bottom-padding"]} ${homeStyles["filters-panel"]}`}>
{/* Keep the FilterButton at the top */}
<div className="mb-4">
<FilterButton onFilterPress={onFilterPress} />
{/* Left Section: Filter Panel with Toggle */}
{!isFilterPanelCollapsed && (
<div className={`flex flex-col w-full bg-white shadow-md p-4 ${homeStyles["no-bottom-padding"]} ${homeStyles["filters-panel"]} transition-all duration-300`}>
{/* Filter Header with Toggle Button */}
<div className="mb-4 flex items-center justify-between">
<FilterButton onFilterPress={onFilterPress} />
<Button
appearance="subtle"
icon={<ChevronLeft24Regular />}
onClick={toggleFilterPanel}
title="Collapse filters"
size="small"
/>
</div>
{areFiltersVisible && (
<Filter
onFilterChanged={onFilterChanged}
prevSelectedFilters={persistedFilters}
keywordFilterInfo={data?.keywordFilterInfo}
clearFilters={clearFilters}
onFilterCleared={handleFilterCleared}
/>
)}
</div>
{areFiltersVisible && (
<Filter
onFilterChanged={onFilterChanged}
prevSelectedFilters={persistedFilters}
keywordFilterInfo={data?.keywordFilterInfo}
clearFilters={clearFilters}
onFilterCleared={handleFilterCleared}
/>
)}
</div>
)}

{/* Right Section: Search Box and Fluent v2 Dropdown */}
<div className={`flex flex-col w-full bg-white shadow-md p-4 ${homeStyles["no-bottom-padding"]} ${homeStyles["documents-panel"]}`}>
{/* Collapsed Filter Toggle Button */}
{isFilterPanelCollapsed && (
<div className="flex flex-col bg-white shadow-md p-2 min-w-[50px] items-center">
<Button
appearance="subtle"
icon={<ChevronRight24Regular />}
onClick={toggleFilterPanel}
title="Expand filters"
size="small"
/>
</div>
)}

{/* Documents Section */}
<div
className={`flex flex-col bg-white shadow-md p-4 ${homeStyles["no-bottom-padding"]} transition-all duration-300`}
style={{ width: `${documentsWidth}%`, minWidth: '300px' }}
>
<div className="flex flex-row items-center space-x-2"> {/* Adjusted space between elements */}
{/* Search Box */}
<SearchBox
Expand Down Expand Up @@ -648,8 +687,19 @@ export function Home({ isSearchResultsPage }: HomeProps) {
</div>
</div>

{/* Panel Resizer */}
<PanelResizer
onResize={handlePanelResize}
initialLeftWidth={documentsWidth}
minLeftWidth={300}
minRightWidth={300}
/>

{/* Chat Room Section */}
<div className={`flex flex-col w-full bg-white shadow-md ${homeStyles["chat-panel"]}`}>
<div
className={`flex flex-col bg-white shadow-md ${homeStyles["chat-panel"]} transition-all duration-300`}
style={{ width: `${chatWidth}%`, minWidth: '300px' }}
>
<div className="flex flex-col h-full">
<div className={`flex-grow overflow-y-auto ${homeStyles["no-bottom-padding"]}` } style={{ backgroundColor: '#f7f7f7', overflowX: 'hidden', overflowY: 'auto', display: 'flex', maxBlockSize: 'calc(100vh - 60px)'}}>
<ChatRoom
Expand Down