mirror of
https://github.com/chartdb/chartdb.git
synced 2025-11-13 10:25:58 +00:00
add drawer to side panel on mobile
This commit is contained in:
16
package-lock.json
generated
16
package-lock.json
generated
@@ -47,7 +47,8 @@
|
|||||||
"react-use": "^17.5.1",
|
"react-use": "^17.5.1",
|
||||||
"tailwind-merge": "^2.4.0",
|
"tailwind-merge": "^2.4.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"timeago-react": "^3.0.6"
|
"timeago-react": "^3.0.6",
|
||||||
|
"vaul": "^0.9.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.1.0",
|
"@types/node": "^22.1.0",
|
||||||
@@ -9484,6 +9485,19 @@
|
|||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/vaul": {
|
||||||
|
"version": "0.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/vaul/-/vaul-0.9.1.tgz",
|
||||||
|
"integrity": "sha512-fAhd7i4RNMinx+WEm6pF3nOl78DFkAazcN04ElLPFF9BMCNGbY/kou8UMhIcicm0rJCNePJP0Yyza60gGOD0Jw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-dialog": "^1.0.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.3.5",
|
"version": "5.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz",
|
||||||
|
|||||||
@@ -51,7 +51,8 @@
|
|||||||
"react-use": "^17.5.1",
|
"react-use": "^17.5.1",
|
||||||
"tailwind-merge": "^2.4.0",
|
"tailwind-merge": "^2.4.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"timeago-react": "^3.0.6"
|
"timeago-react": "^3.0.6",
|
||||||
|
"vaul": "^0.9.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.1.0",
|
"@types/node": "^22.1.0",
|
||||||
|
|||||||
116
src/components/drawer/drawer.tsx
Normal file
116
src/components/drawer/drawer.tsx
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Drawer as DrawerPrimitive } from 'vaul';
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
const Drawer = ({
|
||||||
|
shouldScaleBackground = true,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
|
||||||
|
<DrawerPrimitive.Root
|
||||||
|
shouldScaleBackground={shouldScaleBackground}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
Drawer.displayName = 'Drawer';
|
||||||
|
|
||||||
|
const DrawerTrigger = DrawerPrimitive.Trigger;
|
||||||
|
|
||||||
|
const DrawerPortal = DrawerPrimitive.Portal;
|
||||||
|
|
||||||
|
const DrawerClose = DrawerPrimitive.Close;
|
||||||
|
|
||||||
|
const DrawerOverlay = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DrawerPrimitive.Overlay>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DrawerPrimitive.Overlay
|
||||||
|
ref={ref}
|
||||||
|
className={cn('fixed inset-0 z-50 bg-black/80', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
|
||||||
|
|
||||||
|
const DrawerContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DrawerPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<DrawerPortal>
|
||||||
|
<DrawerOverlay />
|
||||||
|
<DrawerPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
|
||||||
|
{children}
|
||||||
|
</DrawerPrimitive.Content>
|
||||||
|
</DrawerPortal>
|
||||||
|
));
|
||||||
|
DrawerContent.displayName = 'DrawerContent';
|
||||||
|
|
||||||
|
const DrawerHeader = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn('grid gap-1.5 p-4 text-center sm:text-left', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
DrawerHeader.displayName = 'DrawerHeader';
|
||||||
|
|
||||||
|
const DrawerFooter = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn('mt-auto flex flex-col gap-2 p-4', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
DrawerFooter.displayName = 'DrawerFooter';
|
||||||
|
|
||||||
|
const DrawerTitle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DrawerPrimitive.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DrawerPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'text-lg font-semibold leading-none tracking-tight',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
|
||||||
|
|
||||||
|
const DrawerDescription = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DrawerPrimitive.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DrawerPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn('text-sm text-muted-foreground', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Drawer,
|
||||||
|
DrawerPortal,
|
||||||
|
DrawerOverlay,
|
||||||
|
DrawerTrigger,
|
||||||
|
DrawerClose,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerHeader,
|
||||||
|
DrawerFooter,
|
||||||
|
DrawerTitle,
|
||||||
|
DrawerDescription,
|
||||||
|
};
|
||||||
@@ -14,6 +14,10 @@ export interface LayoutContext {
|
|||||||
|
|
||||||
selectedSidebarSection: SidebarSection;
|
selectedSidebarSection: SidebarSection;
|
||||||
selectSidebarSection: (section: SidebarSection) => void;
|
selectSidebarSection: (section: SidebarSection) => void;
|
||||||
|
|
||||||
|
isSidePanelShowed: boolean;
|
||||||
|
hideSidePanel: () => void;
|
||||||
|
showSidePanel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const layoutContext = createContext<LayoutContext>({
|
export const layoutContext = createContext<LayoutContext>({
|
||||||
@@ -27,4 +31,8 @@ export const layoutContext = createContext<LayoutContext>({
|
|||||||
selectSidebarSection: emptyFn,
|
selectSidebarSection: emptyFn,
|
||||||
openTableFromSidebar: emptyFn,
|
openTableFromSidebar: emptyFn,
|
||||||
closeAllTablesInSidebar: emptyFn,
|
closeAllTablesInSidebar: emptyFn,
|
||||||
|
|
||||||
|
isSidePanelShowed: false,
|
||||||
|
hideSidePanel: emptyFn,
|
||||||
|
showSidePanel: emptyFn,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,12 +11,20 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
React.useState<string | undefined>();
|
React.useState<string | undefined>();
|
||||||
const [selectedSidebarSection, setSelectedSidebarSection] =
|
const [selectedSidebarSection, setSelectedSidebarSection] =
|
||||||
React.useState<SidebarSection>('tables');
|
React.useState<SidebarSection>('tables');
|
||||||
|
const [isSidePanelShowed, setIsSidePanelShowed] =
|
||||||
|
React.useState<boolean>(false);
|
||||||
|
|
||||||
const closeAllTablesInSidebar: LayoutContext['closeAllTablesInSidebar'] =
|
const closeAllTablesInSidebar: LayoutContext['closeAllTablesInSidebar'] =
|
||||||
() => setOpenedTableInSidebar('');
|
() => setOpenedTableInSidebar('');
|
||||||
|
|
||||||
const closeAllRelationshipsInSidebar: LayoutContext['closeAllRelationshipsInSidebar'] =
|
const closeAllRelationshipsInSidebar: LayoutContext['closeAllRelationshipsInSidebar'] =
|
||||||
() => setOpenedRelationshipInSidebar('');
|
() => setOpenedRelationshipInSidebar('');
|
||||||
|
|
||||||
|
const hideSidePanel: LayoutContext['hideSidePanel'] = () =>
|
||||||
|
setIsSidePanelShowed(false);
|
||||||
|
|
||||||
|
const showSidePanel: LayoutContext['showSidePanel'] = () =>
|
||||||
|
setIsSidePanelShowed(true);
|
||||||
return (
|
return (
|
||||||
<layoutContext.Provider
|
<layoutContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@@ -28,6 +36,9 @@ export const LayoutProvider: React.FC<React.PropsWithChildren> = ({
|
|||||||
openRelationshipFromSidebar: setOpenedRelationshipInSidebar,
|
openRelationshipFromSidebar: setOpenedRelationshipInSidebar,
|
||||||
closeAllTablesInSidebar,
|
closeAllTablesInSidebar,
|
||||||
closeAllRelationshipsInSidebar,
|
closeAllRelationshipsInSidebar,
|
||||||
|
isSidePanelShowed,
|
||||||
|
hideSidePanel,
|
||||||
|
showSidePanel,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ import { useChartDB } from '@/hooks/use-chartdb';
|
|||||||
import { LEFT_HANDLE_ID_PREFIX, TARGET_ID_PREFIX } from './table-node-field';
|
import { LEFT_HANDLE_ID_PREFIX, TARGET_ID_PREFIX } from './table-node-field';
|
||||||
import { Toolbar } from './toolbar/toolbar';
|
import { Toolbar } from './toolbar/toolbar';
|
||||||
import { useToast } from '@/components/toast/use-toast';
|
import { useToast } from '@/components/toast/use-toast';
|
||||||
|
import { Pencil } from 'lucide-react';
|
||||||
|
import { Button } from '@/components/button/button';
|
||||||
|
import { useLayout } from '@/hooks/use-layout';
|
||||||
|
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
||||||
|
|
||||||
type AddEdgeParams = Parameters<typeof addEdge<TableEdgeType>>[0];
|
type AddEdgeParams = Parameters<typeof addEdge<TableEdgeType>>[0];
|
||||||
|
|
||||||
@@ -41,6 +45,8 @@ export const Canvas: React.FC<CanvasProps> = () => {
|
|||||||
removeRelationships,
|
removeRelationships,
|
||||||
getField,
|
getField,
|
||||||
} = useChartDB();
|
} = useChartDB();
|
||||||
|
const { showSidePanel } = useLayout();
|
||||||
|
const { isMd: isDesktop } = useBreakpoint('md');
|
||||||
const nodeTypes = useMemo(() => ({ table: TableNode }), []);
|
const nodeTypes = useMemo(() => ({ table: TableNode }), []);
|
||||||
const edgeTypes = useMemo(() => ({ 'table-edge': TableEdge }), []);
|
const edgeTypes = useMemo(() => ({ 'table-edge': TableEdge }), []);
|
||||||
|
|
||||||
@@ -250,6 +256,23 @@ export const Canvas: React.FC<CanvasProps> = () => {
|
|||||||
}}
|
}}
|
||||||
panOnScroll
|
panOnScroll
|
||||||
>
|
>
|
||||||
|
{!isDesktop ? (
|
||||||
|
<Controls
|
||||||
|
position="bottom-left"
|
||||||
|
orientation="horizontal"
|
||||||
|
showZoom={false}
|
||||||
|
showFitView={false}
|
||||||
|
showInteractive={false}
|
||||||
|
className="!shadow-none"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className="bg-pink-600 hover:bg-pink-500 w-11 h-11 p-2"
|
||||||
|
onClick={showSidePanel}
|
||||||
|
>
|
||||||
|
<Pencil />
|
||||||
|
</Button>
|
||||||
|
</Controls>
|
||||||
|
) : null}
|
||||||
<Controls
|
<Controls
|
||||||
position="bottom-center"
|
position="bottom-center"
|
||||||
orientation="horizontal"
|
orientation="horizontal"
|
||||||
|
|||||||
@@ -15,9 +15,19 @@ import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
|
|||||||
import { Toaster } from '@/components/toast/toaster';
|
import { Toaster } from '@/components/toast/toaster';
|
||||||
import { useFullScreenLoader } from '@/hooks/use-full-screen-spinner';
|
import { useFullScreenLoader } from '@/hooks/use-full-screen-spinner';
|
||||||
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
||||||
|
import { useLayout } from '@/hooks/use-layout';
|
||||||
|
import {
|
||||||
|
Drawer,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerDescription,
|
||||||
|
DrawerHeader,
|
||||||
|
DrawerTitle,
|
||||||
|
} from '@/components/drawer/drawer';
|
||||||
|
import { Separator } from '@/components/separator/separator';
|
||||||
|
|
||||||
export const EditorPage: React.FC = () => {
|
export const EditorPage: React.FC = () => {
|
||||||
const { loadDiagram, currentDiagram } = useChartDB();
|
const { loadDiagram, currentDiagram } = useChartDB();
|
||||||
|
const { isSidePanelShowed, hideSidePanel } = useLayout();
|
||||||
const { resetRedoStack, resetUndoStack } = useRedoUndoStack();
|
const { resetRedoStack, resetUndoStack } = useRedoUndoStack();
|
||||||
const { showLoader, hideLoader } = useFullScreenLoader();
|
const { showLoader, hideLoader } = useFullScreenLoader();
|
||||||
const { openCreateDiagramDialog } = useDialog();
|
const { openCreateDiagramDialog } = useDialog();
|
||||||
@@ -26,6 +36,7 @@ export const EditorPage: React.FC = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { isLg } = useBreakpoint('lg');
|
const { isLg } = useBreakpoint('lg');
|
||||||
const { isXl } = useBreakpoint('xl');
|
const { isXl } = useBreakpoint('xl');
|
||||||
|
const { isMd: isDesktop } = useBreakpoint('md');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!config) {
|
if (!config) {
|
||||||
@@ -68,21 +79,46 @@ export const EditorPage: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section className="bg-background h-screen w-screen flex flex-col">
|
<section
|
||||||
|
className={`bg-background ${isDesktop ? 'h-screen w-screen' : 'h-dvh w-dvw'} flex flex-col overflow-x-hidden`}
|
||||||
|
>
|
||||||
<TopNavbar />
|
<TopNavbar />
|
||||||
|
{isDesktop ? (
|
||||||
<ResizablePanelGroup direction="horizontal">
|
<ResizablePanelGroup direction="horizontal">
|
||||||
<ResizablePanel
|
<ResizablePanel
|
||||||
defaultSize={isXl ? 25 : isLg ? 35 : 50}
|
defaultSize={isXl ? 25 : isLg ? 35 : 50}
|
||||||
minSize={isXl ? 25 : isLg ? 35 : 50}
|
minSize={isXl ? 25 : isLg ? 35 : 50}
|
||||||
maxSize={99}
|
maxSize={!isSidePanelShowed ? 99 : 0}
|
||||||
>
|
>
|
||||||
<SidePanel />
|
<SidePanel />
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
<ResizableHandle />
|
<ResizableHandle />
|
||||||
<ResizablePanel defaultSize={isXl ? 75 : isLg ? 65 : 50}>
|
<ResizablePanel
|
||||||
|
defaultSize={isXl ? 75 : isLg ? 65 : 50}
|
||||||
|
>
|
||||||
<Canvas />
|
<Canvas />
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
</ResizablePanelGroup>
|
</ResizablePanelGroup>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Drawer
|
||||||
|
open={isSidePanelShowed}
|
||||||
|
onClose={() => hideSidePanel()}
|
||||||
|
>
|
||||||
|
<DrawerContent className="h-full">
|
||||||
|
<DrawerHeader>
|
||||||
|
<DrawerTitle>Manage Diagram</DrawerTitle>
|
||||||
|
<DrawerDescription>
|
||||||
|
Manage your diagram objects
|
||||||
|
</DrawerDescription>
|
||||||
|
</DrawerHeader>
|
||||||
|
<Separator orientation="horizontal" />
|
||||||
|
<SidePanel />
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
<Canvas />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user