owned this note
owned this note
Published
Linked with GitHub
# NOTE系統自動Layout
### 4:3 時

### 16:9 時

### Portrait時

## Claude Artifact
https://claude.site/artifacts/3ffc3d57-a4cd-4064-9feb-3c37c380dbea?fullscreen=true
## Source Code
```javascript
import { useState, useEffect, useCallback, useRef } from 'react';
import { GripVertical, GripHorizontal, PanelLeftClose, PanelLeftOpen } from 'lucide-react';
import { Switch } from '@/components/ui/switch';
const AdjustablePanels = () => {
const [isVertical, setIsVertical] = useState(true);
const [rightPanelVisible, setRightPanelVisible] = useState(true);
const [size, setSize] = useState(50);
const [isDragging, setIsDragging] = useState(false);
const [isUserAdjusted, setIsUserAdjusted] = useState(false);
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const containerRef = useRef(null);
const updateDimensions = useCallback(() => {
if (!containerRef.current) return;
const { width, height } = containerRef.current.getBoundingClientRect();
setDimensions({ width, height });
}, []);
const adjustLayout = useCallback(() => {
if (isUserAdjusted || !containerRef.current) return;
const { width, height } = containerRef.current.getBoundingClientRect();
const aspectRatio = width / height;
if (aspectRatio < 1) {
setIsVertical(false);
setSize(50);
} else if (aspectRatio >= 1.6) {
setIsVertical(true);
setSize(50);
} else {
setIsVertical(true);
setSize(33.33);
}
updateDimensions();
}, [isUserAdjusted, updateDimensions]);
useEffect(() => {
if (!containerRef.current) return;
const observer = new ResizeObserver(() => {
adjustLayout();
updateDimensions();
});
observer.observe(containerRef.current);
return () => observer.disconnect();
}, [adjustLayout, updateDimensions]);
const startDragging = useCallback((e) => {
e.preventDefault();
setIsDragging(true);
setIsUserAdjusted(true);
}, []);
const doDragging = useCallback((e) => {
if (!isDragging || !containerRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
if (isVertical) {
const x = e.clientX - rect.left;
const newSize = (x / rect.width) * 100;
setSize(Math.min(Math.max(newSize, 20), 80));
} else {
const y = e.clientY - rect.top;
const newSize = (y / rect.height) * 100;
setSize(Math.min(Math.max(newSize, 20), 80));
}
updateDimensions();
}, [isDragging, isVertical, updateDimensions]);
const stopDragging = useCallback(() => {
setIsDragging(false);
}, []);
useEffect(() => {
if (isDragging) {
document.addEventListener('mousemove', doDragging);
document.addEventListener('mouseup', stopDragging);
document.body.style.userSelect = 'none';
document.body.style.cursor = isVertical ? 'col-resize' : 'row-resize';
} else {
document.removeEventListener('mousemove', doDragging);
document.removeEventListener('mouseup', stopDragging);
document.body.style.userSelect = '';
document.body.style.cursor = '';
}
return () => {
document.removeEventListener('mousemove', doDragging);
document.removeEventListener('mouseup', stopDragging);
document.body.style.userSelect = '';
document.body.style.cursor = '';
};
}, [isDragging, doDragging, stopDragging, isVertical]);
const handleLayoutChange = (checked) => {
setIsVertical(checked);
setIsUserAdjusted(true);
};
const getPanelSizes = () => {
const { width, height } = dimensions;
if (isVertical) {
const leftWidth = Math.round(width * (size / 100));
const rightWidth = Math.round(width * ((100 - size) / 100));
return {
first: `${leftWidth}px × ${height}px`,
second: `${rightWidth}px × ${height}px`
};
} else {
const topHeight = Math.round(height * (size / 100));
const bottomHeight = Math.round(height * ((100 - size) / 100));
return {
first: `${width}px × ${topHeight}px`,
second: `${width}px × ${bottomHeight}px`
};
}
};
const panelSizes = getPanelSizes();
return (
<div className="h-screen w-full flex flex-col">
<div className="h-12 border-b flex items-center px-4 justify-between bg-white">
<div className="flex items-center gap-2">
<span>布局模式:</span>
<Switch
checked={isVertical}
onCheckedChange={handleLayoutChange}
/>
<span>{isVertical ? '左右分割' : '上下分割'}</span>
</div>
<div className="flex items-center gap-4">
<span>容器:{Math.round(dimensions.width)}px × {Math.round(dimensions.height)}px</span>
<button
onClick={() => setRightPanelVisible(!rightPanelVisible)}
className="p-2 hover:bg-gray-100 rounded-md"
title={rightPanelVisible ? '收起面板' : '展開面板'}
>
{rightPanelVisible ? <PanelLeftClose size={20} /> : <PanelLeftOpen size={20} />}
</button>
</div>
</div>
<div
ref={containerRef}
className={`flex-1 flex ${isVertical ? 'flex-row' : 'flex-col'} overflow-hidden`}
>
<div
className="bg-gray-50 overflow-auto"
style={{
[isVertical ? 'width' : 'height']: rightPanelVisible ? `${size}%` : '100%',
flexShrink: 0
}}
>
<div className="p-4">
{isVertical ? '左側面板內容' : '上方面板內容'}
<div className="mt-2 text-sm text-gray-500">
尺寸:{panelSizes.first}
</div>
</div>
</div>
{rightPanelVisible && (
<div
className={`flex items-center justify-center
${isVertical ?
'w-2 hover:bg-blue-100 cursor-col-resize' :
'h-2 hover:bg-blue-100 w-full cursor-row-resize'}
${isDragging ? 'bg-blue-200' : ''}`}
onMouseDown={startDragging}
onDragStart={(e) => e.preventDefault()}
title="拖動調整大小"
>
{isVertical ? (
<GripVertical size={16} className="text-gray-400" />
) : (
<GripHorizontal size={16} className="text-gray-400" />
)}
</div>
)}
{rightPanelVisible && (
<div
className="bg-gray-50 overflow-auto"
style={{
[isVertical ? 'width' : 'height']: `${100 - size}%`,
flexShrink: 0
}}
>
<div className="p-4">
{isVertical ? '右側面板內容' : '下方面板內容'}
<div className="mt-2 text-sm text-gray-500">
尺寸:{panelSizes.second}
</div>
</div>
</div>
)}
</div>
</div>
);
};
export default AdjustablePanels;
```