Closed MilanObrenovic closed 9 months ago
it's not messing check this https://codesandbox.io/p/devbox/drawer-direction-right-n338ml?file=%2Fapp%2Fmy-drawer.tsx%3A7%2C29-7%2C34
it's not messing check this https://codesandbox.io/p/devbox/drawer-direction-right-n338ml?file=%2Fapp%2Fmy-drawer.tsx%3A7%2C29-7%2C34
you confused me a bit but yeah, it indeed is missing. What is vaul
? you provided an example using some other UI package that is not related to shadcn.
and yeah i just tried that example as well and it still doesnt work. i cannot scroll vertically if there are too many navigation menu items. i can only scroll after adding overflow-y-scroll
to Drawer.Content
, but then the other serious problem that happens is you can't swipe to close the drawer anymore
vaul
is the library that shadcn used to build his drawer I think now I can understand your probleme and I think I can help you if you give me an example in a sandbox
how about using it as a side navbar, where it appears from left side with menu items? This way anyone can swipe to close the navigation on mobile
the example that I shared was for this question
vaul
is the library that shadcn used to build his drawer I think now I can understand your probleme and I think I can help you if you give me an example in a sandbox
i already gave you an example in the original post. check the MUI library's SwipeableDrawer
component. Try to swipe it on mobile from right to left and see how it works perfectly fine. That same drawer is used in this template: https://react-material.fusetheme.com/
Test it on mobile yourself. That is the best UX i have seen yet for the navigation sidebar menu. I want to achieve that same effect with shadcn. Is it possible or not? @shadcn
how about using it as a side navbar, where it appears from left side with menu items? This way anyone can swipe to close the navigation on mobile
the example that I shared was for this question
correct. this is what shadcn's drawer can also do as it turns out, after i modified it a bit. but i forgot to add the part where i need this drawer to have the content scrollable in case there are too many items. but if it gets scrollable then the drag/swipe effect of the drawer no longer works. can you solve this?
@MilanObrenovic
You can use ScrollArea
component from @shadcn to have a scroll and here is the demo
import { ScrollArea } from '@/components/ui/scroll-area';
export function DrawerDemo() {
return (
<Drawer direction='right'>
<DrawerTrigger asChild>
<Button variant='outline'>Open Drawer</Button>
</DrawerTrigger>
<DrawerContent className='h-screen top-0 right-0 left-auto mt-0 w-[500px] rounded-none'>
<ScrollArea className='h-screen'>
<div className='mx-auto w-full p-5'>
<DrawerHeader>
<DrawerTitle>Theme Color Options</DrawerTitle>
<DrawerDescription>
* Selected option will be applied to all layout elements (navbar, toolbar, etc.). You can also create your own theme options and color
schemes.
</DrawerDescription>
</DrawerHeader>
<div className='p-4 pb-0 space-y-4'>
<div className='bg-muted flex items-center justify-center rounded-lg h-32'>
<p>Image 1</p>
</div>
<div className='bg-muted flex items-center justify-center rounded-lg h-32'>
<p>Image 2</p>
</div>
<div className='bg-muted flex items-center justify-center rounded-lg h-32'>
<p>Image 3</p>
</div>
<div className='bg-muted flex items-center justify-center rounded-lg h-32'>
<p>Image 4</p>
</div>
<div className='bg-muted flex items-center justify-center rounded-lg h-32'>
<p>Image 4</p>
</div>
<div className='bg-muted flex items-center justify-center rounded-lg h-32'>
<p>Image 5</p>
</div>
<div className='bg-muted flex items-center justify-center rounded-lg h-32'>
<p>Image 6</p>
</div>
<div className='bg-muted flex items-center justify-center rounded-lg h-32'>
<p>Image 7</p>
</div>
<div className='bg-muted flex items-center justify-center rounded-lg h-32'>
<p>Image 8</p>
</div>
<div className='bg-muted flex items-center justify-center rounded-lg h-32'>
<p>Image 9</p>
</div>
<div className='bg-muted flex items-center justify-center rounded-lg h-32'>
<p>Image 10</p>
</div>
<div className='bg-muted flex items-center justify-center rounded-lg h-32'>
<p>Image 11</p>
</div>
</div>
</div>
</ScrollArea>
</DrawerContent>
</Drawer>
);
}
If you want to the top bar not to display then you should pass a prop to the DrawerContent
and update the component as below
interface DrawerContentProps extends React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content> {
showBar?: boolean;
}
const DrawerContent = React.forwardRef<React.ElementRef<typeof DrawerPrimitive.Content>, DrawerContentProps>(
({ className, children, showBar = true, ...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}
>
{showBar ? <div className='mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted' /> : null}
{children}
</DrawerPrimitive.Content>
</DrawerPortal>
),
);
DrawerContent.displayName = 'DrawerContent';
I don't know why the animation is not being recorded properly but it works 😅
https://github.com/shadcn-ui/ui/assets/150527559/10644fa1-1400-48ac-9694-a95d7ffe7796
Here is the repo for the issue fix https://github.com/imopbuilder/shadcn-ui-issue-fixes/blob/main/src/components/pages/client.tsx
Thank you @imopbuilder your exemple was exactly what i needed
@MilanObrenovic
You can use
ScrollArea
component from @shadcn to have a scroll and here is the demoimport { ScrollArea } from '@/components/ui/scroll-area'; export function DrawerDemo() { return ( <Drawer direction='right'> <DrawerTrigger asChild> <Button variant='outline'>Open Drawer</Button> </DrawerTrigger> <DrawerContent className='h-screen top-0 right-0 left-auto mt-0 w-[500px] rounded-none'> <ScrollArea className='h-screen'> <div className='mx-auto w-full p-5'> <DrawerHeader> <DrawerTitle>Theme Color Options</DrawerTitle> <DrawerDescription> * Selected option will be applied to all layout elements (navbar, toolbar, etc.). You can also create your own theme options and color schemes. </DrawerDescription> </DrawerHeader> <div className='p-4 pb-0 space-y-4'> <div className='bg-muted flex items-center justify-center rounded-lg h-32'> <p>Image 1</p> </div> <div className='bg-muted flex items-center justify-center rounded-lg h-32'> <p>Image 2</p> </div> <div className='bg-muted flex items-center justify-center rounded-lg h-32'> <p>Image 3</p> </div> <div className='bg-muted flex items-center justify-center rounded-lg h-32'> <p>Image 4</p> </div> <div className='bg-muted flex items-center justify-center rounded-lg h-32'> <p>Image 4</p> </div> <div className='bg-muted flex items-center justify-center rounded-lg h-32'> <p>Image 5</p> </div> <div className='bg-muted flex items-center justify-center rounded-lg h-32'> <p>Image 6</p> </div> <div className='bg-muted flex items-center justify-center rounded-lg h-32'> <p>Image 7</p> </div> <div className='bg-muted flex items-center justify-center rounded-lg h-32'> <p>Image 8</p> </div> <div className='bg-muted flex items-center justify-center rounded-lg h-32'> <p>Image 9</p> </div> <div className='bg-muted flex items-center justify-center rounded-lg h-32'> <p>Image 10</p> </div> <div className='bg-muted flex items-center justify-center rounded-lg h-32'> <p>Image 11</p> </div> </div> </div> </ScrollArea> </DrawerContent> </Drawer> ); }
If you want to the top bar not to display then you should pass a prop to the
DrawerContent
and update the component as belowinterface DrawerContentProps extends React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content> { showBar?: boolean; } const DrawerContent = React.forwardRef<React.ElementRef<typeof DrawerPrimitive.Content>, DrawerContentProps>( ({ className, children, showBar = true, ...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} > {showBar ? <div className='mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted' /> : null} {children} </DrawerPrimitive.Content> </DrawerPortal> ), ); DrawerContent.displayName = 'DrawerContent';
I don't know why the animation is not being recorded properly but it works 😅
shadcn-ui-issue-.2602-1.mp4 Here is the repo for the issue fix https://github.com/imopbuilder/shadcn-ui-issue-fixes/blob/main/src/components/pages/client.tsx
no, you did NOT fix ANYTHING here. have you even read my post or the reason for the issue i've created?
especially on mobile.
So I will repeat again:
@MilanObrenovic
I will try it on mobile and figure out the issue
@MilanObrenovic
I will try it on mobile and figure out the issue
i'll illustrate the problem in more details. use this code and test it yourself:
<Drawer direction={"left"}>
<DrawerTrigger asChild>
<Button variant="outline">Open Drawer</Button>
</DrawerTrigger>
<DrawerContent className="h-full w-[35%]">
<DrawerHeader className="text-left">
<DrawerTitle>Citation</DrawerTitle>
<DrawerDescription>
Make sure to check if the given answer is align with the original
source.
</DrawerDescription>
</DrawerHeader>
<Separator />
<ScrollArea className="overflow-auto p-4 break-all">
{Array.from({ length: 10000 }, (_, index) => index + 1)}
</ScrollArea>
<Separator />
<DrawerFooter className="pt-2">
<p className="text-sm italic">
Thank you for <strong>diligently</strong> double checking!
</p>
</DrawerFooter>
</DrawerContent>
</Drawer>
@MilanObrenovic
I have raised an issue with package vaul
, We should wait for the author to resolve
@MilanObrenovic
I have raised an issue with package
vaul
, We should wait for the author to resolve
great, thank you. now we're finally on the same page. i think this problem is due to implementation of the drawer, so only the package maintainer should be able to fix it, since modifying through css, or changing the html structure, does not fix it.
more than likely the scroll area overrides the swipe functionality, so instead of being able to swipe, you can only scroll. this problem has been solved by MUI library, more specifically this https://mui.com/material-ui/react-drawer/#swipeable component, the SwipeableDrawer
. Try it on mobile and see how flawlessly you can scroll AND swipe to close the sidebar
MUI has proven it's possible to do it, so it's up to vaul
or shadcn
to fix this issue as well
@MilanObrenovic ...have you made any progress? Need to do the same thing in my app, you do with your drawer (open from left, close to the left)
@MilanObrenovic ...have you made any progress? Need to do the same thing in my app, you do with your drawer (open from left, close to the left)
they fixed this bug in the latest update of vaul
package, check https://github.com/emilkowalski/vaul/issues/243#issuecomment-1925446818
@MilanObrenovic I'm using Shadcn Drawer component (brand new app), and it appears I still have an issue like this? If I scroll on my phone on a page that has Drawer Triggers, when the drawer opens, I cannot close it by swiping down. Any thoughts?
@MilanObrenovic I think you should work a bit on your communication. This as well as vaul is open source software maintained by people in their free time without getting paid for it. You did not even bother to create a proper reproduction highlighting your issue, and when someone tried to help you instead of starting with a "thank you", you complain about that not immediately solving your issue or understanding you.
Your way is not motivating people to help, I certainly wouldn't bother to provide a technical solution for you with the way you post here. I think this gives a good starting point if you feel like reading further.
It can be done with the help of props, the list of props can be found here
For me the below code worked. It opens the drawer from the right side of the screen.
<DrawerTrigger asChild>
<Button variant="outline">Open Drawer</Button>
</DrawerTrigger>
<DrawerContent className="top-0 mt-0 ml-[50%] rounded-t-none rounded-[10px]">
<div className="mx-auto w-full max-w-sm">
<DrawerHeader>
<DrawerTitle>Move Goal</DrawerTitle>
<DrawerDescription>Set your daily activity goal.</DrawerDescription>
</DrawerHeader>
<div className="p-4 pb-0">
<div className="flex items-center justify-center space-x-2"></div>
<div className="mt-3 h-[120px]"></div>
</div>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose asChild>
<Button variant="outline">Cancel</Button>
</DrawerClose>
</DrawerFooter>
</div>
</DrawerContent>
</Drawer>
<Drawer direction="right">
opens it from right
<DrawerContent className="top-0 mt-0 ml-[50%] rounded-t-none rounded-[10px]">
will make adjustment to it's top and margin on the left, ml-[50%] will make it half of the screen size (horizontally). If you reduce the margin, it'll increase the drawer width.
If you update the below marked line directly in the Drawer component file, you'll be able to move the grabber to left and vertically centre
Please note: I haven't optimised it for mobile devices (smaller screens). You can play with it to meet your needs.
For future reference, the scroll bug has been fixed as of now.
I asked v0 to refer this gh discussion and give me a SideDrawer. So it gave me a working example, anyone in the future looking at this can refer to it → https://v0.dev/chat/gM7F8YekWTV?b=b_rVTuxRJPsyw
This is my implementation.
'use client';
import * as React from 'react';
import { Drawer as DrawerPrimitive } from 'vaul';
import { cva } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const DrawerContext = React.createContext<{ direction?: 'right' | 'top' | 'bottom' | 'left' }>({
direction: 'right',
});
const Drawer = ({
shouldScaleBackground = true,
direction = 'right',
...props
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
<DrawerContext.Provider value={{ direction }}>
<DrawerPrimitive.Root
shouldScaleBackground={shouldScaleBackground}
direction={direction}
{...props}
/>
</DrawerContext.Provider>
);
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 drawerContentVariants = cva('fixed z-50 flex h-auto flex-col border bg-background', {
variants: {
direction: {
right: 'ml-24 right-0 rounded-l-[10px] inset-y-0',
top: 'mb-24 top-0 rounded-b-[10px] inset-x-0',
bottom: 'mt-24 rounded-t-[10px] bottom-0 inset-x-0',
left: 'mr-24 left-0 rounded-r-[10px] inset-y-0',
},
},
defaultVariants: {
direction: 'right',
},
});
const DrawerContent = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
>(({ className, children, ...props }, ref) => {
const { direction } = React.useContext(DrawerContext);
return (
<DrawerPortal>
<DrawerOverlay />
<DrawerPrimitive.Content
ref={ref}
className={cn(drawerContentVariants({ direction, 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,
};
To open from the right, the sheet component works perfectly.
How about having the option to change position of the drawer? I think the drawer is a very cool thing especially with the drag & swipe effect to close it. I think this would be perfect for mobile side navigation bar. So instead of forcing this drawer to be shown from the bottom of the screen, how about using it as a side navbar, where it appears from left side with menu items? This way anyone can swipe to close the navigation on mobile
Example: exactly like this https://mui.com/material-ui/react-drawer/#swipeable
Open this left sidebar in mobile mode and try to swipe it from right to left to close the navigation