rnd panel fixed for mobile devices

This commit is contained in:
Saimon8420 2024-12-30 13:36:14 +06:00
parent 9707f31094
commit 5b3d96c7eb
7 changed files with 233 additions and 168 deletions

View file

@ -1,7 +1,6 @@
import { useEffect, useCallback, useContext } from 'react'; import { useEffect, useCallback, useContext } from 'react';
import CanvasContext from './Context/canvasContext/CanvasContext'; import CanvasContext from './Context/canvasContext/CanvasContext';
import ActiveObjectContext from './Context/activeObject/ObjectContext'; import ActiveObjectContext from './Context/activeObject/ObjectContext';
import { Rnd } from 'react-rnd';
import OpenContext from './Context/openContext/OpenContext'; import OpenContext from './Context/openContext/OpenContext';
import CanvasSetting from './CanvasSetting'; import CanvasSetting from './CanvasSetting';
import { EditPanel } from './EditPanel'; import { EditPanel } from './EditPanel';
@ -79,21 +78,9 @@ const Canvas = () => {
return ( return (
<div className='flex flex-col items-center relative w-full h-full overflow-hidden'> <div className='flex flex-col items-center relative w-full h-full overflow-hidden'>
{openSetting && ( {openSetting &&
<Rnd <CanvasSetting />
className='z-[1000]' }
default={{
x: 0,
y: 20,
}}
minWidth={220}
maxWidth={250}
minHeight={150}
bounds='parent'
>
<CanvasSetting />
</Rnd>
)}
{ {
openPanel && openPanel &&

View file

@ -5,9 +5,9 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardHeader, CardTitle } from './ui/card'; import { Card, CardHeader, CardTitle } from './ui/card';
import { Rnd } from 'react-rnd';
import { X } from 'lucide-react'; import { X } from 'lucide-react';
import { Separator } from './ui/separator'; import { Separator } from './ui/separator';
import RndComponent from './Layouts/RndComponent';
const resolutions = [ const resolutions = [
{ value: '720p', label: 'HD (1280x720)', width: 1280, height: 720 }, { value: '720p', label: 'HD (1280x720)', width: 1280, height: 720 },
@ -73,28 +73,33 @@ const CanvasCapture = () => {
}; };
}; };
const rndValue = {
valueX: 0,
valueY: 20,
width: 250,
height: 150,
minWidth: 250,
maxWidth: 300,
minHeight: 150,
maxHeight: 300,
bound: "parent"
}
return ( return (
<> <>
{captureOpen && ( {captureOpen && (
<Rnd <RndComponent value={rndValue}>
className='z-[1000]'
default={{ x: 0, y: 20 }}
minWidth={250}
maxWidth={300}
minHeight={150}
bounds='parent'
>
<Card className='space-y-2 p-2'> <Card className='space-y-2 p-2'>
<CardHeader className='p-0'> <CardHeader className='p-0'>
<div className='flex items-center justify-between'> <div className='flex items-center justify-between'>
<Button variant={'ghost'} onClick={() => setCaptureOpen(false)}> <Button className="rnd-escape" variant={'ghost'} onClick={() => setCaptureOpen(false)}>
<X className='cursor-pointer' /> <X className='cursor-pointer' />
</Button> </Button>
<CardTitle className='text-lg mr-1'>Capture Panel</CardTitle> <CardTitle className='text-lg mr-1'>Capture Panel</CardTitle>
</div> </div>
</CardHeader> </CardHeader>
<Separator /> <Separator />
<div className='grid grid-cols-1 sm:grid-cols-2 gap-4'> <div className='grid grid-cols-1 sm:grid-cols-2 gap-4 rnd-escape'>
<div className='space-y-2'> <div className='space-y-2'>
<Label htmlFor='resolution'>Resolution</Label> <Label htmlFor='resolution'>Resolution</Label>
<Select onValueChange={setResolution} defaultValue={resolution}> <Select onValueChange={setResolution} defaultValue={resolution}>
@ -126,11 +131,11 @@ const CanvasCapture = () => {
</Select> </Select>
</div> </div>
</div> </div>
<Button onClick={captureImage} className='w-full'> <Button onClick={captureImage} className='w-full rnd-escape'>
Capture Canvas Capture Canvas
</Button> </Button>
</Card> </Card>
</Rnd> </RndComponent>
)} )}
</> </>
); );

View file

@ -10,6 +10,8 @@ import { Input } from './ui/input';
import { Slider } from './ui/slider'; import { Slider } from './ui/slider';
import { fabric } from 'fabric'; import { fabric } from 'fabric';
import OpenContext from './Context/openContext/OpenContext'; import OpenContext from './Context/openContext/OpenContext';
import { ScrollArea } from './ui/scroll-area';
import RndComponent from './Layouts/RndComponent';
const CanvasSetting = () => { const CanvasSetting = () => {
const { canvas } = useContext(CanvasContext); const { canvas } = useContext(CanvasContext);
@ -155,104 +157,120 @@ const CanvasSetting = () => {
} }
}; };
const rndValue = {
valueX: 0,
valueY: 20,
width: 250,
height: 0,
minWidth: 250,
maxWidth: 300,
minHeight: 0,
maxHeight: 400,
bound: "parent"
}
return ( return (
<Card className="px-2 py-2"> <RndComponent value={rndValue}>
<CardTitle className="flex items-center flex-wrap justify-between gap-1">Canvas Setting <Button variant="secondary" onClick={() => setOpenSetting(false)}><X /></Button> </CardTitle> <Card className="px-2 py-2">
<Separator className="mt-4" /> <CardTitle className="flex items-center flex-wrap justify-between gap-1">Canvas Setting <Button className="rnd-escape" variant="secondary" onClick={() => setOpenSetting(false)}><X /></Button> </CardTitle>
<div>
<ColorComponent />
<div className='flex flex-col my-2 gap-2'>
<div>
<Label>Background:</Label>
<div className='flex items-center w-fit gap-2 flex-wrap relative'>
<Button className="top-0 absolute flex items-center w-[30px]">
<UploadIcon className="cursor-pointer" />
<Input
ref={bgImgRef}
className="absolute top-0 opacity-0"
type="file"
accept="image/*"
onChange={setBackgroundImage}
/>
</Button>
<Button variant="secondary" className="ml-[35px]" onClick={removeBackgroundImage}><Trash2 /></Button>
</div>
</div>
<div>
<Label>Background Overlay:</Label>
<div className='flex items-center w-fit gap-2 flex-wrap relative'>
<Button className="top-0 absolute flex items-center w-[30px]">
<UploadIcon className="cursor-pointer" />
<Input
className="absolute top-0 opacity-0"
type="file"
accept="image/*"
onChange={setBackgroundOverlayImage}
/>
</Button>
<Button variant="secondary" className="ml-[35px]" onClick={removeBackgroundOverlayImage}><Trash2 /></Button>
</div>
</div>
<Separator className="mt-4" />
<div className='flex flex-col gap-2'>
<Label>
Adjust Opacity:
</Label>
<Slider
defaultValue={[1.0]} // Default value, you can set it to 0.0 or another value
min={0.0}
max={1.0}
step={0.01} // Step size for fine control
onValueChange={(value) => {
const newOpacity = value[0]; // Extract slider value
adjustBackgroundOpacity(newOpacity); // Adjust Fabric.js background opacity
}}
/>
</div>
</div>
<Separator className="mt-4" /> <Separator className="mt-4" />
<ScrollArea className="h-[400px] xl:h-fit lg:h-fit md:h-fit">
<div className='rnd-escape'>
<div className='flex gap-2 my-2'> <ColorComponent />
<div className='flex flex-col gap-2'>
<Label>
Width:
</Label>
<Input
type="number"
value={canvasSettings.width}
onChange={(e) => {
if (canvasSettings?.width > parseInt(e.target.value)) {
handleChange('width', parseInt(e.target.value, 10));
}
}}
/>
</div>
<div className='flex flex-col gap-2'> <div className='flex flex-col my-2 gap-2 rnd-escape'>
<Label> <div>
Height: <Label>Background:</Label>
</Label> <div className='flex items-center w-fit gap-2 flex-wrap relative'>
<Input <Button className="top-0 absolute flex items-center w-[30px]">
type="number" <UploadIcon className="cursor-pointer" />
value={canvasSettings.height} <Input
onChange={(e) => { ref={bgImgRef}
if (canvasSettings?.height > parseInt(e.target.value)) { className="absolute top-0 opacity-0"
handleChange('height', parseInt(e.target.value, 10)); type="file"
} accept="image/*"
}} onChange={setBackgroundImage}
/> />
</Button>
<Button variant="secondary" className="ml-[35px]" onClick={removeBackgroundImage}><Trash2 /></Button>
</div>
</div>
<div>
<Label>Background Overlay:</Label>
<div className='flex items-center w-fit gap-2 flex-wrap relative'>
<Button className="top-0 absolute flex items-center w-[30px]">
<UploadIcon className="cursor-pointer" />
<Input
className="absolute top-0 opacity-0"
type="file"
accept="image/*"
onChange={setBackgroundOverlayImage}
/>
</Button>
<Button variant="secondary" className="ml-[35px]" onClick={removeBackgroundOverlayImage}><Trash2 /></Button>
</div>
</div>
<Separator className="mt-4" />
<div className='flex flex-col gap-2 rnd-escape'>
<Label>
Adjust Opacity:
</Label>
<Slider
defaultValue={[1.0]} // Default value, you can set it to 0.0 or another value
min={0.0}
max={1.0}
step={0.01} // Step size for fine control
onValueChange={(value) => {
const newOpacity = value[0]; // Extract slider value
adjustBackgroundOpacity(newOpacity); // Adjust Fabric.js background opacity
}}
/>
</div>
</div>
<Separator className="mt-4" />
<div className='flex gap-2 my-2'>
<div className='flex flex-col gap-2'>
<Label>
Width:
</Label>
<Input
type="number"
value={canvasSettings.width}
onChange={(e) => {
if (canvasSettings?.width > parseInt(e.target.value)) {
handleChange('width', parseInt(e.target.value, 10));
}
}}
/>
</div>
<div className='flex flex-col gap-2'>
<Label>
Height:
</Label>
<Input
type="number"
value={canvasSettings.height}
onChange={(e) => {
if (canvasSettings?.height > parseInt(e.target.value)) {
handleChange('height', parseInt(e.target.value, 10));
}
}}
/>
</div>
</div>
</div> </div>
</div> </ScrollArea>
</div> </Card>
</Card> </RndComponent>
) )
} }

View file

@ -1,4 +1,3 @@
import { Rnd } from 'react-rnd';
import { useCallback, useContext, useState } from "react" import { useCallback, useContext, useState } from "react"
import { Card, CardContent, CardHeader, CardTitle } from './ui/card'; import { Card, CardContent, CardHeader, CardTitle } from './ui/card';
import { Button } from './ui/button'; import { Button } from './ui/button';
@ -10,6 +9,7 @@ import OpenContext from './Context/openContext/OpenContext';
import CanvasContext from './Context/canvasContext/CanvasContext'; import CanvasContext from './Context/canvasContext/CanvasContext';
import ActiveObjectContext from './Context/activeObject/ObjectContext'; import ActiveObjectContext from './Context/activeObject/ObjectContext';
import { fabric } from 'fabric'; import { fabric } from 'fabric';
import RndComponent from './Layouts/RndComponent';
export function EditPanel() { export function EditPanel() {
const [isCollapsed, setIsCollapsed] = useState(false); const [isCollapsed, setIsCollapsed] = useState(false);
@ -189,31 +189,31 @@ export function EditPanel() {
} }
}; };
const rndValue = {
valueX: 0,
valueY: 20,
width: 250,
height: 0,
minWidth: 250,
maxWidth: 300,
minHeight: 0,
maxHeight: 0,
bound: "parent"
}
return ( return (
<Rnd <RndComponent value={rndValue}>
default={{
x: 0,
y: 20,
width: 300,
height: 'auto',
}}
minWidth={200}
maxWidth={300}
bounds="parent"
enableResizing={{ right: true }}
className="z-[1000]"
>
<Card className="w-full shadow-lg"> <Card className="w-full shadow-lg">
<CardHeader className="p-1 mr-12"> <CardHeader className="p-1 mr-12">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<Button variant={"ghost"} onClick={() => setOpenPanel(false)}><X /></Button> <Button className="rnd-escape" variant={"ghost"} onClick={() => setOpenPanel(false)}><X /></Button>
<CardTitle className="text-lg">Edit Panel</CardTitle> <CardTitle className="text-lg">Edit Panel</CardTitle>
</div> </div>
</CardHeader> </CardHeader>
<Collapsible open={!isCollapsed} onOpenChange={(open) => setIsCollapsed(!open)}> <Collapsible open={!isCollapsed} onOpenChange={(open) => setIsCollapsed(!open)}>
<CollapsibleTrigger asChild> <CollapsibleTrigger asChild>
<Button variant="ghost" size="sm" className="absolute top-1 right-2"> <Button variant="ghost" size="sm" className="absolute top-1 right-2 rnd-escape">
{isCollapsed ? <ChevronDown className="h-4 w-4" /> : <ChevronUp className="h-4 w-4" />} {isCollapsed ? <ChevronDown className="h-4 w-4" /> : <ChevronUp className="h-4 w-4" />}
</Button> </Button>
</CollapsibleTrigger> </CollapsibleTrigger>
@ -373,7 +373,7 @@ export function EditPanel() {
</CollapsibleContent> </CollapsibleContent>
</Collapsible> </Collapsible>
</Card> </Card>
</Rnd> </RndComponent>
) )
} }

View file

@ -0,0 +1,28 @@
import { Rnd } from 'react-rnd';
const RndComponent = ({ children, value }) => {
const { valueX, valueY, width, height, minWidth, maxWidth, minHeight, maxHeight, bound } = value;
return (
<Rnd
default={{
x: valueX,
y: valueY,
width: width,
height: 'auto',
}}
minWidth={minWidth}
maxWidth={maxWidth}
maxHeight={maxHeight}
bounds={bound}
className="z-[1000]"
cancel=".rnd-escape"
>
{children}
</Rnd>
)
}
export default RndComponent

View file

@ -1,10 +1,9 @@
import { Button } from './ui/button' import { Button } from './ui/button'
import { Card, CardContent, CardHeader, CardTitle } from './ui/card' import { Card, CardHeader, CardTitle } from './ui/card'
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/collapsible' import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/collapsible'
import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs' import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs'
import { useContext, useState } from 'react' import { useContext, useState } from 'react'
import { PencilRuler, Shapes, Store, Upload, ChevronDown, ChevronUp, X } from 'lucide-react'; import { PencilRuler, Shapes, Store, Upload, ChevronDown, ChevronUp, X } from 'lucide-react';
import { Rnd } from 'react-rnd';
import OpenContext from './Context/openContext/OpenContext'; import OpenContext from './Context/openContext/OpenContext';
import AllIconsPage from './EachComponent/Icons/AllIcons' import AllIconsPage from './EachComponent/Icons/AllIcons'
import { Separator } from './ui/separator' import { Separator } from './ui/separator'
@ -12,34 +11,35 @@ import { ScrollArea } from './ui/scroll-area'
import AddShapes from './Layouts/AddShapes' import AddShapes from './Layouts/AddShapes'
import UploadImage from './EachComponent/UploadImage' import UploadImage from './EachComponent/UploadImage'
import CustomizeShape from './EachComponent/CustomizeShape' import CustomizeShape from './EachComponent/CustomizeShape'
import RndComponent from './Layouts/RndComponent'
const ObjectPanel = () => { const ObjectPanel = () => {
const [isCollapsed, setIsCollapsed] = useState(false); const [isCollapsed, setIsCollapsed] = useState(false);
const { tabValue, setTabValue, setOpenObjectPanel } = useContext(OpenContext); const { tabValue, setTabValue, setOpenObjectPanel } = useContext(OpenContext);
const rndValue = {
valueX: 0,
valueY: 20,
width: 250,
height: 0,
minWidth: 250,
maxWidth: 300,
minHeight: 0,
maxHeight: 400,
bound: "parent"
}
return ( return (
<Rnd <RndComponent value={rndValue}>
default={{
x: 0,
y: 20,
width: 300,
height: 'auto',
}}
minWidth={270}
maxWidth={300}
maxHeight={500}
bounds="parent"
enableResizing={{ right: true }}
className="z-40"
>
<Card className="w-full shadow-lg"> <Card className="w-full shadow-lg">
<CardHeader className="p-1 mr-12"> <CardHeader className="p-1 mr-12">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<Button variant={"ghost"} size={"sm"} onClick={() => setOpenObjectPanel(false)}><X /></Button> <Button className="rnd-escape" variant={"ghost"} size={"sm"} onClick={() => setOpenObjectPanel(false)}><X /></Button>
<CardTitle className="text-lg">Object Panel</CardTitle> <CardTitle className="text-lg">Object Panel</CardTitle>
</div> </div>
</CardHeader> </CardHeader>
<Collapsible open={!isCollapsed} onOpenChange={(open) => setIsCollapsed(!open)}> <Collapsible className="rnd-escape" open={!isCollapsed} onOpenChange={(open) => setIsCollapsed(!open)}>
<CollapsibleTrigger asChild> <CollapsibleTrigger asChild>
<Button variant="ghost" size="sm" className="absolute top-1 right-2"> <Button variant="ghost" size="sm" className="absolute top-1 right-2">
@ -48,21 +48,48 @@ const ObjectPanel = () => {
</CollapsibleTrigger> </CollapsibleTrigger>
<CollapsibleContent> <CollapsibleContent>
<CardContent className="p-2"> <ScrollArea className="h-[400px] xl:h-fit lg:h-fit md:h-fit">
<Tabs className="w-full h-fit" <Tabs className="w-full h-fit"
value={tabValue} value={tabValue}
onValueChange={(value) => setTabValue(value)} // Sync tab state with context onValueChange={(value) => setTabValue(value)} // Sync tab state with context
> >
<TabsList className="grid w-full grid-cols-4 h-fit gap-1"> <TabsList className="grid w-full grid-cols-4 h-fit gap-1">
<TabsTrigger value="icons" className="flex flex-col gap-1 text-xs" onClick={() => setTabValue("icons")}> <TabsTrigger
<Store className="h-4 w-4" />Icons value="icons"
className="flex flex-col gap-1 text-xs"
onClick={() => setTabValue("icons")}
>
<Store className="h-4 w-4" />
<span className="hidden sm:block">Icons</span>
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="shapes" className="flex flex-wrap gap-1 text-xs" onClick={() => setTabValue("shapes")}><Shapes className="h-4 w-4" />Shapes</TabsTrigger> <TabsTrigger
<TabsTrigger value="images" className="flex flex-wrap gap-1 text-xs" onClick={() => setTabValue("images")}><Upload className="h-4 w-4" />Images</TabsTrigger> value="shapes"
className="flex flex-col gap-1 text-xs"
onClick={() => setTabValue("shapes")}
>
<Shapes className="h-4 w-4" />
<span className="hidden sm:block">Shapes</span>
</TabsTrigger>
<TabsTrigger value="customize" className="flex flex-wrap gap-1 text-xs" onClick={() => setTabValue("customize")}><PencilRuler className="h-4 w-4" />Customize</TabsTrigger> <TabsTrigger
value="images"
className="flex flex-col gap-1 text-xs"
onClick={() => setTabValue("images")}
>
<Upload className="h-4 w-4" />
<span className="hidden sm:block">Images</span>
</TabsTrigger>
<TabsTrigger
value="customize"
className="flex flex-col gap-1 text-xs"
onClick={() => setTabValue("customize")}
>
<PencilRuler className="h-4 w-4" />
<span className="hidden sm:block">Customize</span>
</TabsTrigger>
</TabsList> </TabsList>
{/* All icons */} {/* All icons */}
@ -109,7 +136,7 @@ const ObjectPanel = () => {
</Tabs> </Tabs>
</CardContent> </ScrollArea>
</CollapsibleContent> </CollapsibleContent>
@ -117,7 +144,7 @@ const ObjectPanel = () => {
</Card> </Card>
</Rnd> </RndComponent>
) )
} }

View file

@ -8,7 +8,7 @@ const ScrollArea = React.forwardRef(({ className, children, ...props }, ref) =>
ref={ref} ref={ref}
className={cn("relative overflow-hidden", className)} className={cn("relative overflow-hidden", className)}
{...props}> {...props}>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]"> <ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit] p-1">
{children} {children}
</ScrollAreaPrimitive.Viewport> </ScrollAreaPrimitive.Viewport>
<ScrollBar /> <ScrollBar />
@ -24,9 +24,9 @@ const ScrollBar = React.forwardRef(({ className, orientation = "vertical", ...pr
className={cn( className={cn(
"flex touch-none select-none transition-colors", "flex touch-none select-none transition-colors",
orientation === "vertical" && orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]", "h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" && orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]", "h-2.5 flex-col border-t border-t-transparent p-[1px]",
className className
)} )}
{...props}> {...props}>