fix: sidebar layout on mobile
This commit is contained in:
parent
92cef83508
commit
852ab28e31
3 changed files with 60 additions and 16 deletions
|
|
@ -8,6 +8,7 @@ import { ToolContext } from "@/context/Tool";
|
||||||
|
|
||||||
import { useBlockData } from "@/hooks/useBlockData";
|
import { useBlockData } from "@/hooks/useBlockData";
|
||||||
import { useTextures } from "@/hooks/useTextures";
|
import { useTextures } from "@/hooks/useTextures";
|
||||||
|
import { Application } from "pixi.js";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
stageWidth: number;
|
stageWidth: number;
|
||||||
|
|
@ -19,6 +20,8 @@ function BlockSelector({ stageWidth, searchInput }: Props) {
|
||||||
const { isDark } = useContext(ThemeContext);
|
const { isDark } = useContext(ThemeContext);
|
||||||
const { selectedBlock, setSelectedBlock } = useContext(ToolContext);
|
const { selectedBlock, setSelectedBlock } = useContext(ToolContext);
|
||||||
|
|
||||||
|
const [app, setApp] = useState<Application>();
|
||||||
|
|
||||||
const [hoverPosition, setHoverPosition] = useState<Position | null>(null);
|
const [hoverPosition, setHoverPosition] = useState<Position | null>(null);
|
||||||
const [selectedBlockPosition, setSelectedBlockPosition] = useState<Position | null>({ x: 0, y: 0 });
|
const [selectedBlockPosition, setSelectedBlockPosition] = useState<Position | null>({ x: 0, y: 0 });
|
||||||
|
|
||||||
|
|
@ -44,6 +47,13 @@ function BlockSelector({ stageWidth, searchInput }: Props) {
|
||||||
setSelectedBlockPosition(position);
|
setSelectedBlockPosition(position);
|
||||||
}, [searchInput, selectedBlock]);
|
}, [searchInput, selectedBlock]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!app?.renderer?.view?.style) return;
|
||||||
|
|
||||||
|
// Can't set it in props for some reason
|
||||||
|
app.renderer.view.style.touchAction = "auto";
|
||||||
|
}, [app]);
|
||||||
|
|
||||||
if (filteredBlocks.length == 0) {
|
if (filteredBlocks.length == 0) {
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full flex flex-col justify-center items-center gap-1 text-zinc-400">
|
<div className="w-full h-full flex flex-col justify-center items-center gap-1 text-zinc-400">
|
||||||
|
|
@ -59,12 +69,18 @@ function BlockSelector({ stageWidth, searchInput }: Props) {
|
||||||
height={Math.ceil(Object.keys(blockData).length / blocksPerColumn) * (32 + 2) + 8}
|
height={Math.ceil(Object.keys(blockData).length / blocksPerColumn) * (32 + 2) + 8}
|
||||||
options={{ backgroundAlpha: 0 }}
|
options={{ backgroundAlpha: 0 }}
|
||||||
onMouseLeave={() => setHoverPosition(null)}
|
onMouseLeave={() => setHoverPosition(null)}
|
||||||
|
onMount={setApp}
|
||||||
>
|
>
|
||||||
<Container>
|
<Container>
|
||||||
{filteredBlocks.map((block, index) => {
|
{filteredBlocks.map((block, index) => {
|
||||||
const texture = textures[block];
|
const texture = textures[block];
|
||||||
const { x, y } = getBlockPosition(index);
|
const { x, y } = getBlockPosition(index);
|
||||||
|
|
||||||
|
const onClick = () => {
|
||||||
|
setSelectedBlock(block);
|
||||||
|
setSelectedBlockPosition({ x, y });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sprite
|
<Sprite
|
||||||
key={block}
|
key={block}
|
||||||
|
|
@ -73,11 +89,9 @@ function BlockSelector({ stageWidth, searchInput }: Props) {
|
||||||
y={y}
|
y={y}
|
||||||
scale={2}
|
scale={2}
|
||||||
eventMode={"static"}
|
eventMode={"static"}
|
||||||
pointerover={() => setHoverPosition({ x, y })}
|
mouseover={() => setHoverPosition({ x, y })}
|
||||||
click={() => {
|
click={onClick}
|
||||||
setSelectedBlock(block);
|
tap={onClick}
|
||||||
setSelectedBlockPosition({ x, y });
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useContext, useEffect, useRef, useState } from "react";
|
import { useContext, useEffect, useRef, useState } from "react";
|
||||||
import { GripVerticalIcon } from "lucide-react";
|
import { isMobile, useMobileOrientation } from "react-device-detect";
|
||||||
|
import { GripHorizontalIcon, GripVerticalIcon } from "lucide-react";
|
||||||
|
|
||||||
import { SettingsContext } from "@/context/Settings";
|
import { SettingsContext } from "@/context/Settings";
|
||||||
|
|
||||||
|
|
@ -17,6 +18,12 @@ import BlockSelector from "./BlockSelector";
|
||||||
function Sidebar() {
|
function Sidebar() {
|
||||||
const { settings } = useContext(SettingsContext);
|
const { settings } = useContext(SettingsContext);
|
||||||
|
|
||||||
|
const { isLandscape } = useMobileOrientation();
|
||||||
|
const isMobileView = isMobile && !isLandscape;
|
||||||
|
|
||||||
|
// For mobile
|
||||||
|
const [height, setHeight] = useState(300);
|
||||||
|
// For horizontal screens
|
||||||
const [width, setWidth] = useState(300);
|
const [width, setWidth] = useState(300);
|
||||||
const [resizing, setResizing] = useState(false);
|
const [resizing, setResizing] = useState(false);
|
||||||
|
|
||||||
|
|
@ -31,17 +38,22 @@ function Sidebar() {
|
||||||
|
|
||||||
const onMouseDown = () => {
|
const onMouseDown = () => {
|
||||||
setResizing(true);
|
setResizing(true);
|
||||||
document.body.style.cursor = "ew-resize";
|
document.body.style.cursor = isMobileView ? "ns-resize" : "ew-resize";
|
||||||
|
document.body.style.touchAction = "none";
|
||||||
|
document.body.style.userSelect = "none";
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMouseUp = () => {
|
const onMouseUp = () => {
|
||||||
setResizing(false);
|
setResizing(false);
|
||||||
document.body.style.cursor = "auto";
|
document.body.style.cursor = "auto";
|
||||||
|
document.body.style.touchAction = "auto";
|
||||||
|
document.body.style.userSelect = "auto";
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMouseMove = (e: MouseEvent) => {
|
const onMouseMove = (e: PointerEvent) => {
|
||||||
if (!resizing) return;
|
if (!resizing) return;
|
||||||
setWidth(() => Math.min(Math.max(window.innerWidth - e.clientX, 200), 1000));
|
if (isMobileView) setHeight(() => Math.min(Math.max(window.innerHeight - e.clientY, 200), 500));
|
||||||
|
setWidth(() => Math.min(Math.max(window.innerWidth - e.clientX, 200), 500));
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -83,14 +95,21 @@ function Sidebar() {
|
||||||
<>
|
<>
|
||||||
{(settings.historyPanel || settings.colorPicker || settings.blockReplacer || settings.toolSettings || settings.blockSelector) && (
|
{(settings.historyPanel || settings.colorPicker || settings.blockReplacer || settings.toolSettings || settings.blockSelector) && (
|
||||||
<div
|
<div
|
||||||
style={{ width: `${width}px` }}
|
style={{ width: `${isMobileView ? "auto" : `${width}px`}`, height: `${isMobileView ? `${height}px` : "auto"}` }}
|
||||||
className="w-72 border-l border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 p-2 pb-0 flex flex-col h-full gap-2 relative"
|
className={`border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 p-2 pb-0 flex flex-col h-full gap-2 relative ${
|
||||||
|
isMobileView ? "row-start-3 col-span-full border-t w-full overflow-y-auto" : "border-l w-72"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="absolute top-0 -left-2 h-full w-4 bg-zinc-300 dark:bg-zinc-800 flex justify-center items-center cursor-e-resize opacity-0 transition-opacity duration-300 delay-100 hover:opacity-100"
|
onTouchStart={onMouseDown}
|
||||||
onPointerDown={onMouseDown}
|
onPointerDown={onMouseDown}
|
||||||
|
className={`absolute bg-zinc-300 dark:bg-zinc-800 top-0 z-10 flex justify-center items-center opacity-0 transition-opacity duration-300 delay-100 ${
|
||||||
|
isMobileView
|
||||||
|
? `${resizing ? "opacity-100" : "opacity-0"} left-0 h-4 w-full cursor-s-resize`
|
||||||
|
: "-left-2 h-full w-4 cursor-e-resize hover:opacity-100"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<GripVerticalIcon />
|
{isMobileView ? <GripHorizontalIcon /> : <GripVerticalIcon />}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(settings.historyPanel || settings.colorPicker || settings.blockReplacer) && (
|
{(settings.historyPanel || settings.colorPicker || settings.blockReplacer) && (
|
||||||
|
|
@ -127,14 +146,14 @@ function Sidebar() {
|
||||||
{settings.toolSettings && (
|
{settings.toolSettings && (
|
||||||
<>
|
<>
|
||||||
<ToolSettings />
|
<ToolSettings />
|
||||||
<Separator />
|
{settings.blockSelector && <Separator />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{settings.blockSelector && (
|
{settings.blockSelector && (
|
||||||
<>
|
<>
|
||||||
<Input placeholder="Search for blocks..." value={searchInput} onChange={(e) => setSearchInput(e.target.value)} />
|
<Input placeholder="Search for blocks..." value={searchInput} onChange={(e) => setSearchInput(e.target.value)} />
|
||||||
<ScrollArea ref={divRef} className="w-full flex-1 pb-0">
|
<ScrollArea ref={divRef} className={`w-full flex-1 pb-0 ${isMobileView ? "min-h-48" : ""}`}>
|
||||||
<BlockSelector stageWidth={stageWidth} searchInput={searchInput} />
|
<BlockSelector stageWidth={stageWidth} searchInput={searchInput} />
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { isMobile, useMobileOrientation } from "react-device-detect";
|
||||||
|
|
||||||
import { CanvasProvider } from "@/context/Canvas";
|
import { CanvasProvider } from "@/context/Canvas";
|
||||||
import { HistoryProvider } from "@/context/History";
|
import { HistoryProvider } from "@/context/History";
|
||||||
import { LoadingProvider } from "@/context/Loading";
|
import { LoadingProvider } from "@/context/Loading";
|
||||||
|
|
@ -13,6 +15,9 @@ import Canvas from "@/components/canvas/Canvas";
|
||||||
import Sidebar from "@/components/sidebar";
|
import Sidebar from "@/components/sidebar";
|
||||||
|
|
||||||
function AppPage() {
|
function AppPage() {
|
||||||
|
const { isLandscape } = useMobileOrientation();
|
||||||
|
const isMobileView = isMobile && !isLandscape;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoadingProvider>
|
<LoadingProvider>
|
||||||
<SettingsProvider>
|
<SettingsProvider>
|
||||||
|
|
@ -23,7 +28,13 @@ function AppPage() {
|
||||||
<SelectionProvider>
|
<SelectionProvider>
|
||||||
<MobileNotice />
|
<MobileNotice />
|
||||||
|
|
||||||
<main className="overflow-y-hidden h-screen grid grid-rows-[2.5rem_minmax(0,1fr)] grid-cols-[2.5rem_minmax(0,1fr)_auto]">
|
<main
|
||||||
|
className={`overflow-y-hidden h-[100svh] grid ${
|
||||||
|
isMobileView
|
||||||
|
? "grid-rows-[2.5rem_minmax(0,1fr)_auto] grid-cols-[2.5rem_minmax(0,1fr)]"
|
||||||
|
: "grid-rows-[2.5rem_minmax(0,1fr)] grid-cols-[2.5rem_minmax(0,1fr)_auto]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<Menubar />
|
<Menubar />
|
||||||
<Toolbar />
|
<Toolbar />
|
||||||
<Canvas />
|
<Canvas />
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue