diff --git a/web/src/beta/components/Resizable/hooks.ts b/web/src/beta/components/Resizable/hooks.ts index 258edad9b..d257b4bdb 100644 --- a/web/src/beta/components/Resizable/hooks.ts +++ b/web/src/beta/components/Resizable/hooks.ts @@ -1,7 +1,7 @@ import React, { useCallback, useState, useEffect, useMemo } from "react"; -type Direction = "vertical" | "horizontal"; -type Gutter = "start" | "end"; +export type Direction = "vertical" | "horizontal"; +export type Gutter = "start" | "end"; const getPositionFromEvent = (e: React.MouseEvent | React.TouchEvent) => { const { nativeEvent } = e; @@ -18,7 +18,6 @@ const getPositionFromEvent = (e: React.MouseEvent | React.TouchEvent) => { if (nativeEvent instanceof TouchEvent) { const touch = nativeEvent.touches[0]; const { clientX: x, clientY: y } = touch; - return { x, y }; } @@ -28,32 +27,31 @@ const getPositionFromEvent = (e: React.MouseEvent | React.TouchEvent) => { const getDelta = (direction: Direction, deltaX: number, deltaY: number) => direction === "vertical" ? deltaX : deltaY; -const getSize = (size: number, delta: number, minSize?: number, maxSize?: number) => { +const getSize = (size: number, delta: number, minSize?: number) => { if (minSize !== undefined && size + delta < minSize) return minSize; - if (maxSize !== undefined && size + delta > maxSize) return maxSize; return size + delta; }; -export default ( - direction: Direction, - gutter: Gutter, - initialSize: number, - minSize?: number, - maxSize?: number, -) => { +export default (direction: Direction, gutter: Gutter, initialSize: number, minSize: number) => { + const [startingSize, setStartingSize] = useState(initialSize); + const [isResizing, setIsResizing] = useState(false); const [size, setSize] = useState(initialSize); const [position, setPosition] = useState({ x: 0, y: 0 }); + const [minimized, setMinimized] = useState(false); + + const [difference, setDifference] = useState(0); const onResizeStart = useCallback( (e: React.MouseEvent | React.TouchEvent) => { const position = getPositionFromEvent(e); if (!position) return; + setStartingSize(size); setIsResizing(true); setPosition(position); }, - [], + [size], ); const onResize = useCallback( @@ -65,10 +63,29 @@ export default ( const deltaY = gutter === "start" ? position.y - y : y - position.y; const delta = getDelta(direction, deltaX, deltaY); - setSize(getSize(size, delta, minSize, maxSize)); + const newDiff = difference + delta; + + setDifference(newDiff); + + setSize(getSize(size, delta, minSize)); + + if (!minimized && startingSize + newDiff <= minSize / 2) { + setMinimized(true); + } setPosition({ x, y }); }, - [isResizing, position, direction, gutter, size, minSize, maxSize], + [ + isResizing, + gutter, + position.x, + position.y, + direction, + size, + startingSize, + minSize, + minimized, + difference, + ], ); const onResizeEnd = useCallback(() => { @@ -76,7 +93,9 @@ export default ( setIsResizing(false); setPosition({ x: 0, y: 0 }); - }, [isResizing]); + setStartingSize(size); + setDifference(0); + }, [isResizing, size]); const bindEventListeners = useCallback(() => { if (typeof window === "undefined") return; @@ -113,5 +132,10 @@ export default ( [onResizeStart], ); - return { size, gutterProps }; + const handleResetSize = useCallback(() => { + setMinimized(false); + setSize(initialSize); + }, [initialSize]); + + return { size: size, gutterProps, minimized, handleResetSize }; }; diff --git a/web/src/beta/components/Resizable/index.stories.tsx b/web/src/beta/components/Resizable/index.stories.tsx index d34b2dd96..0210be606 100644 --- a/web/src/beta/components/Resizable/index.stories.tsx +++ b/web/src/beta/components/Resizable/index.stories.tsx @@ -22,9 +22,8 @@ export const Vertical: StoryObj = { args: { direction: "vertical", gutter: "end", - size: 400, - minSize: 300, - maxSize: 500, + initialSize: 400, + minSize: 100, }, render: args => { return ( @@ -40,9 +39,8 @@ export const Horizontal: StoryObj = { args: { direction: "horizontal", gutter: "end", - size: 200, - minSize: 100, - maxSize: 300, + initialSize: 350, + minSize: 200, }, render: args => { return ( diff --git a/web/src/beta/components/Resizable/index.tsx b/web/src/beta/components/Resizable/index.tsx index 749210940..fad2e6f40 100644 --- a/web/src/beta/components/Resizable/index.tsx +++ b/web/src/beta/components/Resizable/index.tsx @@ -1,27 +1,25 @@ import { ReactNode } from "react"; +import Icon from "@reearth/classic/components/atoms/Icon"; import { styled } from "@reearth/services/theme"; -import useHooks from "./hooks"; +import useHooks, { type Direction, type Gutter } from "./hooks"; type Props = { children?: ReactNode; - direction: "vertical" | "horizontal"; - gutter: "start" | "end"; - size: number; - minSize?: number; - maxSize?: number; + direction: Direction; + gutter: Gutter; + initialSize: number; + minSize: number; }; -const Resizable: React.FC = ({ - direction, - gutter, - size: initialSize, - minSize, - maxSize, - children, -}) => { - const { size, gutterProps } = useHooks(direction, gutter, initialSize, minSize, maxSize); +const Resizable: React.FC = ({ direction, gutter, minSize, initialSize, children }) => { + const { size, gutterProps, minimized, handleResetSize } = useHooks( + direction, + gutter, + initialSize, + minSize, + ); const showTopGutter = direction === "horizontal" && gutter === "start"; const showRightGutter = direction === "vertical" && gutter === "end"; @@ -34,23 +32,39 @@ const Resizable: React.FC = ({ const LeftGutter = showLeftGutter ? : null; return ( - - {TopGutter} - {LeftGutter} - {children} - {RightGutter} - {BottomGutter} - + <> + {minimized ? ( + + + + ) : ( + + {TopGutter} + {LeftGutter} + {children} + {RightGutter} + {BottomGutter} + + )} + ); }; -const StyledResizable = styled.div>` +const StyledResizable = styled.div<{ + direction: "vertical" | "horizontal"; + size: number; + minSize?: number; +}>` display: flex; align-items: stretch; flex-direction: ${({ direction }) => (direction === "vertical" ? "row" : "column")}; width: ${({ direction, size }) => (direction === "horizontal" ? null : `${size}px`)}; height: ${({ direction, size }) => (direction === "vertical" ? null : `${size}px`)}; flex-shrink: 0; + min-width: ${({ direction, minSize }) => + direction === "vertical" && minSize ? `${minSize}px` : null}; + min-height: ${({ direction, minSize }) => + direction === "horizontal" && minSize ? `${minSize}px` : null}; `; const Wrapper = styled.div` @@ -74,4 +88,17 @@ const VerticalGutter = styled(Gutter)` cursor: col-resize; `; +const MinimizedWrapper = styled.div>` + display: flex; + align-items: center; + width: ${({ direction }) => (direction === "horizontal" ? null : `24px`)}; + height: ${({ direction }) => (direction === "vertical" ? null : `24px`)}; + background: ${({ theme }) => theme.general.bg.weak}; + cursor: pointer; + transition: background 0.3s; + + :hover { + background: ${({ theme }) => theme.general.bg.veryWeak}; + } +`; export default Resizable; diff --git a/web/src/beta/features/Editor/index.tsx b/web/src/beta/features/Editor/index.tsx index 87c89014c..b93f94908 100644 --- a/web/src/beta/features/Editor/index.tsx +++ b/web/src/beta/features/Editor/index.tsx @@ -34,9 +34,8 @@ const Editor: React.FC = ({ sceneId, projectId, workspaceId, tab }) => { + initialSize={metrics.propertyMenuWidth} + minSize={metrics.propertyMenuMinWidth}> {leftPanel} )} @@ -51,9 +50,8 @@ const Editor: React.FC = ({ sceneId, projectId, workspaceId, tab }) => { + initialSize={metrics.propertyMenuWidth} + minSize={metrics.propertyMenuMinWidth}> {rightPanel} )} diff --git a/web/src/services/theme/reearthTheme/common/metrics.ts b/web/src/services/theme/reearthTheme/common/metrics.ts index ef10c0d80..dcc67fad1 100644 --- a/web/src/services/theme/reearthTheme/common/metrics.ts +++ b/web/src/services/theme/reearthTheme/common/metrics.ts @@ -1,6 +1,6 @@ const metrics = { - propertyMenuMinWidth: 272, - propertyMenuMaxWidth: 336, + propertyMenuWidth: 308, + propertyMenuMinWidth: 200, }; export const metricsSizes = {