shadcn-ui / ui

Beautifully designed components that you can copy and paste into your apps. Accessible. Customizable. Open Source.
https://ui.shadcn.com
MIT License
74.57k stars 4.63k forks source link

Drawer position property missing #2602

Closed MilanObrenovic closed 9 months ago

MilanObrenovic commented 9 months ago

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

kal07 commented 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

MilanObrenovic commented 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

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

kal07 commented 9 months ago

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

kal07 commented 9 months ago

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

MilanObrenovic commented 9 months ago

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

MilanObrenovic commented 9 months ago

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?

imopbuilder commented 9 months ago

@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

Last-Autumn-Leaf commented 9 months ago

Thank you @imopbuilder your exemple was exactly what i needed

MilanObrenovic commented 9 months ago

@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 😅

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?

  1. open the drawer, try to swipe it to close it --> it works
  2. open the drawer, scroll, now try to swipe it --> it does NOT work

especially on mobile.

So I will repeat again:

I want to be able to scroll within the drawer AND ALSO be able to swipe to close it.

imopbuilder commented 9 months ago

@MilanObrenovic

I will try it on mobile and figure out the issue

MilanObrenovic commented 9 months ago

@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>
  1. imagine that you have a left-side navigation menu
  2. open the drawer
  3. swipe from Right to Left => it works
  4. open the drawer again
  5. scroll
  6. swipe from Right to Left => it does NOT work <--- FIX THIS
imopbuilder commented 9 months ago

@MilanObrenovic

I have raised an issue with package vaul, We should wait for the author to resolve

MilanObrenovic commented 9 months ago

@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

jspm2013 commented 9 months ago

@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 commented 9 months ago

@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

Tigatok commented 4 months ago

@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?

matzeso commented 3 months ago

@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.

SaurabhBorse commented 3 months ago

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

image

Please note: I haven't optimised it for mobile devices (smaller screens). You can play with it to meet your needs.

vsg24 commented 2 months ago

For future reference, the scroll bug has been fixed as of now.

zeshhaan commented 1 month ago

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

HFinger36 commented 1 month ago

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,
};
ephraimduncan commented 1 month ago

To open from the right, the sheet component works perfectly.

https://ui.shadcn.com/docs/components/sheet