emilkowalski / vaul Goto Github PK
View Code? Open in Web Editor NEWAn unstyled drawer component for React.
Home Page: https://vaul.emilkowal.ski
License: MIT License
An unstyled drawer component for React.
Home Page: https://vaul.emilkowal.ski
License: MIT License
I'm hoping to use this as a replacement for react-spring-bottom-sheet. Would it be possible to support multiple snap points and a non-blocking mode to match the functionality in this example?
Here's an example of my use case. Live demo
hey,
Does it's possible to keep snapoints enabled, but with dismissible={true}.
Regards
On the latest version of vaul (0.6.3) the [vaul-drawer-wrapper]
is not resetting the transform upon close.
Hey, I just want to propose a section in README.md explaining on how to test components that use the drawer.
I had an issue testing one of my components where jest/testing-library would error out because JSOM does not have a shim of visualViewport
and HTMLElement.prototype.setPointerCapture
.
I have added the following to a setup-after-env.js
file and now tests work!
/**
* JSDOM doesn't support visualViewport, so we need to mock it globally.
*/
global.visualViewport = {
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
height: 800, // Any height you would like
width: 400, // Any width you would like
}
/**
* JSDOM doesn't support HTMLElement.prototype.setPointerCapture, so we need to mock it globally.
*/
global.HTMLElement.prototype.setPointerCapture = jest.fn()
Probably not the best implementation, but it works well enough.
Dear emilkowalski
How to apply animation for height changes when a child component with varying heights is swapped?
If you load up any of the sandboxes from the readme and trigger the drawer to open, the first tab-able element within the drawer does not receive focus. This is actually breaking the a11y radix gives us. The focus is actually remaining on the trigger. Any tab-able elements after the trigger will receive focus first as you tab around before eventually landing in the drawer with the focus trap.
Here is a video of the behavior as well as a sandbox which is a direct fork of the example:
https://codesandbox.io/p/sandbox/drawer-with-scale-forked-6ss9nx?file=/app/page.tsx:8,10
Hey there! Thinking about user experience and all the different devices out there, do you think we could have an option to let the drawer slide from any side, for example, from right to left or top to bottom? And when someone's on a PC, maybe it could pop up as a modal? This sounds a bit easier as I saw you were using Radix Dialog as the base. It just seems like that might look better on bigger screens. If for any reason you can't do it or aren't interested, could you maybe give me a quick pointer on how I might approach it? What do you think? I'd really appreciate your thoughts and help on this.
I have a button that opens up a drawer near the bottom of a page.
Whenever I open the drawer, it will automatically scroll to the top of the page, which is unwanted behavior. Can you make this opt in?
The code I used is the Without scaled background codesandbox example and I'm using Next JS.
experiencing a weird background scroll when the tray is open
context: recently refactored the element scrolling to be the document root, rather than a scrollable div a few children down. problem only started occurring after this change
seeing it happen on iOS safari, mobile chrome, embedded instagram browser... wondering if it is a vaul thing or a radix thing or a browser thing
Although it is intended to be a React component, could I use it in Svelte?
This is the code for the drawer, the error occurs on IOS 16.6.1 and I'm using Next.js with app route
"use client";
import { RainbowSvg } from "@/app/_components/RainbowSvg";
import { Button, cn } from "@nextui-org/react";
import { useState } from "react";
import { Drawer } from "vaul";
export default function Features() {
const [snap, setSnap] = useState<number | string | null>("70%");
return (
<Drawer.Root
shouldScaleBackground
experimentalSafariThemeAnimation
snapPoints={[0.7, 1]}
activeSnapPoint={snap}
setActiveSnapPoint={setSnap}
>
<Drawer.Trigger asChild>
<Button variant="light" size="sm" isIconOnly radius="full">
<RainbowSvg>
<path
d="M21.947 9.179a1.001 1.001 0 0 0-.868-.676l-5.701-.453-2.467-5.461a.998.998 0 0 0-1.822-.001L8.622 8.05l-5.701.453a1 1 0 0 0-.619 1.713l4.213 4.107-1.49 6.452a1 1 0 0 0 1.53 1.057L12 18.202l5.445 3.63a1.001 1.001 0 0 0 1.517-1.106l-1.829-6.4 4.536-4.082c.297-.268.406-.686.278-1.065z"
fill="white"
></path>
</RainbowSvg>
</Button>
</Drawer.Trigger>
<Drawer.Overlay className="fixed inset-0 bg-black/40" />
<Drawer.Portal>
<Drawer.Content className="fixed flex flex-col bg-white border border-gray-200 border-b-none rounded-t-[10px] bottom-0 left-0 right-0 h-full max-h-[97%] mx-[-1px]">
<div
className={cn("flex flex-col max-w-md mx-auto w-full p-4 pt-5", {
"overflow-y-auto": snap === 1,
"overflow-hidden": snap !== 1,
})}
>
<div>hgallo</div>
</div>
</Drawer.Content>
</Drawer.Portal>
</Drawer.Root>
);
}
Is it possible to add support for having elements outside the drawer to be interactable, the "modal" prop for Dialog Root might do it - https://www.radix-ui.com/primitives/docs/components/dialog#root
When triggering the component on mobile devices using Chrome, and there's an element exceeding the screen's width, the browser automatically adds a padding-right
to the html
tag. On other browsers the drawer exhibits a lag before it's positioned correctly.
Steps to reproduce:
This issue is less likely to be spotted on fully responsive websites.
When testing the example with scrollable content and inputs, the keyboard pushes the field so much to the top that a user cannot see what's being typed: https://73f8jw-3000.csb.app/
If you try to drag it a bit down to get the field into view it closes the whole drawer. In the codebox example the lowest field is possible. I am not sure if there's an easy tailwind fix or if this goes a bit deeper.
I have a use case where I need to have a controlled nested root. In my situation I need to programmatically close the nested root. Can you expose the open
prop and forward it to the root component?
Lines 725 to 753 in ecb3e24
Hi, I've been using Vaul as a dialog replacement for some forms. The issue I've been running into is that once there's a couple of input fields, the submit button is past the 75% innerHeight
threshold, which means any misclick and drag, no matter how small, will cause the Drawer to close.
I've been able to implement a workaround by adding onMouseEnter
and onMouseLeave
handlers on the button to toggle the dismissible
prop, but it isn't foolproof as a misclick outside the button will still cause it to close.
Would it be possible to add a prop to fine-tune this innerHeight
threshold and optionally disable it if required.
If:
... in that case, I was expecting the drawer to increase it's height by the keyboard height.
I tried to fix it by setting the drawer max-height as height when the keyboard is open. That kind of works:
https://codesandbox.io/p/sandbox/drawer-scrollable-forked-4878pm?file=%2Fapp%2Fmy-drawer.tsx%3A6%2C29
Also: I've recognized that #21 arises randomly (have seen it on ios and android). My guess is that it happens when an input at the bottom of the drawer content is focused. In that case, browsers seems to add extra margin to the drawer bottom, in order to push the input above the keyboard.
Btw: Issue #21 constantly arises on ios when focusing the bottom input in the "fixed" example.
Hello,
When I set the modal property on the Drawer's Root component to true, clicking outside of the drawer still causes it to close.
I believe the issue is in onPointerDownOutside
prop of DialogPrimitive.Content
in src/index.tsx
, but I wasn't able to get the library to build on my machine, so haven't tested it.
Thanks
I would like to use multiple drawers one on top of the other and close them accordingly. Is this possible in the current version?
If you open the drawer with the trigger being in overflowed part of the page (you have to scroll to see it) on Firefox, you cannot swipe it down to close it. As far as i've tested, the bug does not appear in Chrome or Safari.
Firefox 117.0 (Desktop)
Firefox 116.3.0 (Mobile)
Vaul version 0.4.5 (latest at the time of writing)
Bug occurs on both firefox versions
Open the codesandbox example below while using Firefox and try to swipe down the drawer after opening it
https://codesandbox.io/p/sandbox/misty-snowflake-gp8kfc
The codesandbox features drawer markup from the "With scaled background" readme example but the issue also occurs without the scaling background
Hello I was wondering if there's a way to properly disable the default animations. I've been using this component as a base for our Drawer component and I absolutely love it since it fits most our needs. I've added the ability to position the components on different parts of the screen "top" | "right" | "bottom" | "left"
and have added the styles for it, however i would like to also add the corresponding sliding animations. (when positioned at top, it should slide in from the top etc...)
The problem is that the [vaul-drawer]
and [vaul-drawer][data-state="closed"]
css classes are taking priority over my own styles, and if i try to set "animation: none"
on the component, that rule is always taking priority and no new animations are being applied. However, if I try to add my own animations without removing the default vaul animations using "animation: none"
, some of the vaul-animation settings like duration or transform, which my animation styles may not override are still being applied.
I know in the future you might add the positioning as a feature yourself but is there a way to remove the default animations instead of manually overriding all the css properties set by [vaul-drawer]
and [vaul-drawer][data-state="open"]
classes? any advice is appreciated.
I have a lot of content within the drawer, and want to make it possible to scroll down (by dragging up).
Seems like this is currently not possible, and is cut off at the end of the screen without any possibility to scroll beyond the height of the screen.
I found a solution that involves Radix's ScrollArea component, but when that is implemented, we're unable to drag to close the drawer component.
Is there any way to implement this functionality, so we can have scrollable content within the drawer, while still maintaining the "drag to close" functionality?
Hello
Thanks for a very good library!
Are there any plans to make an option for Vue 2/3?
Like I thought it can be used like a side drawer on mobile or so!
Hello there,
Thank you for the amazing stuff.
We found a couple of issues during the usage.
The first one is background scaling is not restoring the position of the background after manual navigation on latest Chrome.
The second one is in iOS Safari body scroll locked after manual navigation. The reason is locking styles (position: fixed !important; top: 0px; left: 0px; right: 0px; background-color: black;
) remain on the body
tag on the iOS Safari 15.5/16.6
The same issues with react-router v6.
Here is the example on Codesandbox.
I noticed that in the shouldDrag function there is a check element.role === "dialog", but in the firefox browser it does not work, the value can only be obtained through element.getAttribute("role"), which is why the cycle of getting parentNode reaches html, and if the page has a scroll, then shouldDrag returns false.
Example to reproduce (problem only in firefox browser on pc):
https://codesandbox.io/p/sandbox/drawer-without-scale-forked-7w3sl4?file=%2Fapp%2Fmy-drawer.tsx%3A12%2C29
The peer dependencies are set to React v18. Is it possible to support React 17.x too ? Radix does support React 16-18.
Radix default behavior for dialog is it should focus the first focusable element for accessibility purposes. I tested vaul and the trap focus function seems to work, but the autofocus doesnt for some reason. Is this on purpose? if not I would like to try and fix it
Hi,
Inside snapPoints={["150px", "290px", "650px", 1]}
, if I remove the last 1, the drawer still be openable at 100% but without animation, does it's possible to prevent an opening of 100%?
Regards
Steps to reproduce:
Result: can't control the drawer with drag gestures
Expected result: drawer should still be controllable via drag gesture
Browser: iOS Safari 16.5
need keyboard height calculation fix in my product version.
I really love this project , and I am willing to use it in the future .
Just one thing , it would be better if you could change the cursor to 'grab' when you're hovering on that icon in the top :
And also , change it to grabbing , when you're grabbing it to the bottom (I couldn't take a screenshot of it :) )
I'm not sure what's going on with this, perhaps you can help me look. I only upgraded from 0.6.5 - 0.6.7 and between these two versions, my drawers are no long sliding up, they just appear. The closing animation works just fine.
I tried replicating this in a sandbox, but I haven't had any luck in doing so. I just lifted the example off your website and ran it in my env and it doesn't animate in anymore.
Any ideas as to where I might be able to look?
Here is my my two files:
// layout.tsx
export default async function RootLayout({ children, params }: {
children: React.ReactNode;
params: { locale: Locale };
}) {
const locale = params.locale;
return (
<html
className={ `motion-safe:!scroll-smooth ${inter.variable}` }
dir={ locale === 'ar' ? 'rtl' : undefined }
lang={ locale }
suppressHydrationWarning>
<body>
<main vaul-drawer-wrapper="">{ children }</main>
</body>
</html>
);
}
// page.tsx
'use client';
import { Drawer } from 'vaul';
export default function HomePage() {
return (
<Drawer.Root shouldScaleBackground>
<Drawer.Trigger asChild>
<button
className="rounded-full bg-white px-4 py-2.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
type="button">
Open Drawer
</button>
</Drawer.Trigger>
<Drawer.Portal>
<Drawer.Overlay className="fixed inset-0 bg-black/40" />
<Drawer.Content className="fixed inset-x-0 bottom-0 mt-24 flex h-full max-h-[96%] flex-col rounded-t-[10px] bg-gray-100">
<div className="flex-1 rounded-t-[10px] bg-white p-4">
<div className="mx-auto mb-8 h-1.5 w-12 shrink-0 rounded-full bg-gray-300" />
<div className="mx-auto max-w-md">
<Drawer.Title className="mb-4 font-medium">Drawer for React.</Drawer.Title>
<p className="mb-2 text-gray-600">
This component can be used as a Dialog replacement on mobile and tablet devices.
</p>
<p className="mb-2 text-gray-600">
It comes unstyled, has gesture-driven animations, and is made by{ ' ' }
<a
className="underline"
href="https://emilkowal.ski/"
target="_blank">
Emil Kowalski
</a>
.
</p>
<p className="mb-8 text-gray-600">
It uses{ ' ' }
<a
className="underline"
href="https://www.radix-ui.com/docs/primitives/components/dialog"
target="_blank">
Radix's Dialog primitive
</a>{ ' ' }
under the hood and is inspired by{ ' ' }
<a
className="underline"
href="https://twitter.com/devongovett/status/1674470185783402496"
target="_blank">
this tweet.
</a>
</p>
</div>
</div>
<div className="mt-auto border-t border-gray-200 bg-gray-100 p-4">
<div className="mx-auto flex max-w-md justify-end gap-6">
<a
className="gap-0.25 flex items-center text-xs text-gray-600"
href="https://github.com/emilkowalski/vaul"
target="_blank">
GitHub
<svg
aria-hidden="true"
className="ml-1 h-3 w-3"
fill="none"
height="16"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="16">
<path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" />
<path d="M15 3h6v6" />
<path d="M10 14L21 3" />
</svg>
</a>
<a
className="gap-0.25 flex items-center text-xs text-gray-600"
href="https://twitter.com/emilkowalski_"
target="_blank">
Twitter
<svg
aria-hidden="true"
className="ml-1 h-3 w-3"
fill="none"
height="16"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="16">
<path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" />
<path d="M15 3h6v6" />
<path d="M10 14L21 3" />
</svg>
</a>
</div>
</div>
</Drawer.Content>
</Drawer.Portal>
</Drawer.Root>
);
}
I absolutely love this library, and it’s super helpful for app-like interaction on mobile, however;
I think there’s no debate that it’s not as useful on desktop.
My suggestion is to create a side drawer instead of a bottom sheet when Vaul is opened on desktop, it would function virtually the same but it would rest at (and animate out of) the right of the screen.
Regardless, this library is great. Thanks.
Starting a drag gesture with a single finger and then initiating another touch causes the drawer to jump. I'd expect for the drawer to maintain it's position and upgrade to a multi-touch gesture. I've recorded the following video on an Android phone to demonstrate:
Non-modal drawer was fixed recently, but it looks like clicking on a button that is outside of the drawer still closes it. I tried to use stopPropagation()
and preventDefault()
on the drawer to no avail.
Thanks, Emil!
Hi! Thanks for the awesome library!
I encountered a bug with 0.6.6 version that causes next build
to throw TypeError: (void 0) is not a function
. Going back to 0.6.5 fixes the issue.
My node.js version: 18.16.0
TypeError: (void 0) is not a function
at Je (/home/ubuntu/apps/xd/releases/xd/node_modules/vaul/dist/index.js:3:9113)
There are multiple bugs when using input on mobile: I tested with (safari)
Steps to reproduce:
Result: drawer will blink on closing.
Expected result: drawer should close smoothly without blinking.
Browser: IOS Safari 16.0.2
The usePreventScroll()
hook makes use of useLayoutEffect()
when the <Drawer.Root>
component is mounted. For anyone not using NextJs with server components or explicitly mounting on client side only, this will throw SSR warnings. An easy fix could be to switch this out for an isomorphic layout effect.
import { useEffect, useLayoutEffect } from 'react'
export const useIsomorphicLayoutEffect =
typeof window !== 'undefined' ? useLayoutEffect : useEffect
Some tests to avoid regression would be useful.
Here's an example of how that might look like - https://github.com/emilkowalski/sonner/tree/main/test.
Description
Sometimes, after dragging horizontally, the drawer cannot be closed by dragging downwards. As you can see from the video, it doesn't happen every time.
Steps
Environment
Browser: Firefox (cannot reproduce in Chrome or WebKit)
Hey,
First of all, thank you for this awesome drawer component! Really enjoying it so far. Sadly there is one thing that prevents me from using it inside my app:
I am experiencing a weird behavior when clicking on an input field inside the drawer component on my iOS device. The drawer itself is pushed down to the bottom and not visible anymore.
Here is a short demonstration. It uses the bare setup for a next.js app and only typescript, tailwindcss and vaul are installed. The styling of the drawer is similar to the Scrollable with inputs example:
Here is the repository for it:
noticing a delay in body scrollability after closing the drawer, maybe a half second or so
pictured: after the first close of the menu, I try to scroll the body and it is locked. after the second, i wait half a second, and it works.
is there a setting I can pass to decrease this threshold? noticed there was scrollLockThreshold
but that looks like it's more for the content inside the drawer instead of the scroll lock on the body content
it closes on touches with nestedRoot
In my own app I've implemented something where it uses a normal dialog for larger screens and then the bottom sheet for smaller screens, but would imo be cool if it was supported out of the box
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.