Extract Base UI Drawer into the v2 kit
Promote the portal burger shell into a reusable Drawer matching Dialog (thin Base UI + tv slots). TopBarMobileNav now composes the kit parts. Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
@@ -18,7 +18,6 @@
|
|||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
// SOFTWARE.
|
// SOFTWARE.
|
||||||
|
|
||||||
import { Drawer } from "@base-ui/react/drawer";
|
|
||||||
import {
|
import {
|
||||||
BellIcon,
|
BellIcon,
|
||||||
BellRingingIcon,
|
BellRingingIcon,
|
||||||
@@ -29,6 +28,14 @@ import {
|
|||||||
} from "@phosphor-icons/react";
|
} from "@phosphor-icons/react";
|
||||||
import { Button } from "@probo/ui/src/v2/Button/Button";
|
import { Button } from "@probo/ui/src/v2/Button/Button";
|
||||||
import { Link } from "@probo/ui/src/v2/Button/Link";
|
import { Link } from "@probo/ui/src/v2/Button/Link";
|
||||||
|
import { Drawer } from "@probo/ui/src/v2/Drawer/Drawer";
|
||||||
|
import { DrawerBody } from "@probo/ui/src/v2/Drawer/DrawerBody";
|
||||||
|
import { DrawerClose } from "@probo/ui/src/v2/Drawer/DrawerClose";
|
||||||
|
import { DrawerFooter } from "@probo/ui/src/v2/Drawer/DrawerFooter";
|
||||||
|
import { DrawerHeader } from "@probo/ui/src/v2/Drawer/DrawerHeader";
|
||||||
|
import { DrawerPopup } from "@probo/ui/src/v2/Drawer/DrawerPopup";
|
||||||
|
import { DrawerTitle } from "@probo/ui/src/v2/Drawer/DrawerTitle";
|
||||||
|
import { DrawerTrigger } from "@probo/ui/src/v2/Drawer/DrawerTrigger";
|
||||||
import { IconButton } from "@probo/ui/src/v2/IconButton/IconButton";
|
import { IconButton } from "@probo/ui/src/v2/IconButton/IconButton";
|
||||||
import { Text } from "@probo/ui/src/v2/typography/Text";
|
import { Text } from "@probo/ui/src/v2/typography/Text";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -43,7 +50,7 @@ import { useSubscribeDialog } from "#/lib/mailingList/subscribeDialogContext";
|
|||||||
|
|
||||||
import type { TopBarMobileNav_identity$key } from "./__generated__/TopBarMobileNav_identity.graphql";
|
import type { TopBarMobileNav_identity$key } from "./__generated__/TopBarMobileNav_identity.graphql";
|
||||||
import { TOP_BAR_NAV_ITEMS } from "./navItems";
|
import { TOP_BAR_NAV_ITEMS } from "./navItems";
|
||||||
import { topBar, topBarMobileNav } from "./variants";
|
import { topBar } from "./variants";
|
||||||
|
|
||||||
const topBarMobileNavFragment = graphql`
|
const topBarMobileNavFragment = graphql`
|
||||||
fragment TopBarMobileNav_identity on Identity {
|
fragment TopBarMobileNav_identity on Identity {
|
||||||
@@ -72,7 +79,6 @@ export function TopBarMobileNav({ identityKey }: TopBarMobileNavProps) {
|
|||||||
const identity = useFragment(topBarMobileNavFragment, identityKey);
|
const identity = useFragment(topBarMobileNavFragment, identityKey);
|
||||||
|
|
||||||
const barSlots = topBar();
|
const barSlots = topBar();
|
||||||
const slots = topBarMobileNav();
|
|
||||||
|
|
||||||
const displayName = identity
|
const displayName = identity
|
||||||
? (identity.fullName.trim() || identity.email)
|
? (identity.fullName.trim() || identity.email)
|
||||||
@@ -81,9 +87,9 @@ export function TopBarMobileNav({ identityKey }: TopBarMobileNavProps) {
|
|||||||
const close = () => setOpen(false);
|
const close = () => setOpen(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer.Root open={open} onOpenChange={setOpen} swipeDirection="right">
|
<Drawer open={open} onOpenChange={setOpen} swipeDirection="right">
|
||||||
<div className={barSlots.menuTrigger()}>
|
<div className={barSlots.menuTrigger()}>
|
||||||
<Drawer.Trigger
|
<DrawerTrigger
|
||||||
render={(
|
render={(
|
||||||
<IconButton
|
<IconButton
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -96,117 +102,110 @@ export function TopBarMobileNav({ identityKey }: TopBarMobileNavProps) {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Drawer.Portal>
|
<DrawerPopup side="right">
|
||||||
<Drawer.Backdrop className={slots.backdrop()} />
|
<DrawerHeader>
|
||||||
<Drawer.Viewport className={slots.viewport()}>
|
<DrawerTitle>{t("topBar.menuTitle")}</DrawerTitle>
|
||||||
<Drawer.Popup className={slots.popup()}>
|
<DrawerClose
|
||||||
<Drawer.Content className={slots.content()}>
|
render={(
|
||||||
<div className={slots.header()}>
|
<IconButton
|
||||||
<Drawer.Title className={slots.title()}>
|
variant="ghost"
|
||||||
{t("topBar.menuTitle")}
|
color="neutral"
|
||||||
</Drawer.Title>
|
size={2}
|
||||||
<Drawer.Close
|
aria-label={t("topBar.closeMenu")}
|
||||||
render={(
|
>
|
||||||
<IconButton
|
<XIcon />
|
||||||
variant="ghost"
|
</IconButton>
|
||||||
color="neutral"
|
)}
|
||||||
size={2}
|
/>
|
||||||
aria-label={t("topBar.closeMenu")}
|
</DrawerHeader>
|
||||||
>
|
|
||||||
<XIcon />
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<nav className={slots.nav()}>
|
<DrawerBody>
|
||||||
{TOP_BAR_NAV_ITEMS.map(item => (
|
<nav className="contents">
|
||||||
<Link
|
{TOP_BAR_NAV_ITEMS.map(item => (
|
||||||
key={item.to}
|
<Link
|
||||||
to={item.to}
|
key={item.to}
|
||||||
|
to={item.to}
|
||||||
|
variant="ghost"
|
||||||
|
color="neutral"
|
||||||
|
size={3}
|
||||||
|
active={isActive(pathname, item.to)}
|
||||||
|
className="w-full justify-start"
|
||||||
|
onClick={close}
|
||||||
|
>
|
||||||
|
{t(item.labelKey)}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
</DrawerBody>
|
||||||
|
|
||||||
|
<DrawerFooter>
|
||||||
|
{identity == null
|
||||||
|
? (
|
||||||
|
<Button
|
||||||
|
variant="solid"
|
||||||
|
color="neutral"
|
||||||
|
highContrast
|
||||||
|
size={3}
|
||||||
|
className="w-full"
|
||||||
|
iconStart={<LockSimpleIcon />}
|
||||||
|
onClick={() => {
|
||||||
|
close();
|
||||||
|
openSignIn({ continueTo: buildRequestAllContinueUrl() });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("topBar.getAccess")}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<>
|
||||||
|
{displayName
|
||||||
|
? (
|
||||||
|
<div className="flex flex-col gap-0.5 px-1 pb-2">
|
||||||
|
<Text size={2} weight="medium" color="neutral" highContrast className="truncate">
|
||||||
|
{displayName}
|
||||||
|
</Text>
|
||||||
|
<Text size={1} color="faint" className="truncate">
|
||||||
|
{identity.email}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
: null}
|
||||||
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
color="neutral"
|
color={isSubscribed ? "green" : "gold"}
|
||||||
size={3}
|
size={3}
|
||||||
active={isActive(pathname, item.to)}
|
|
||||||
className="w-full justify-start"
|
className="w-full justify-start"
|
||||||
onClick={close}
|
iconStart={isSubscribed ? <BellRingingIcon /> : <BellIcon />}
|
||||||
|
disabled={isUnsubscribing}
|
||||||
|
onClick={() => {
|
||||||
|
close();
|
||||||
|
if (isSubscribed) {
|
||||||
|
void unsubscribe();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openSubscribe();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{t(item.labelKey)}
|
{isSubscribed ? t("userMenu.subscribed") : t("userMenu.subscribe")}
|
||||||
</Link>
|
</Button>
|
||||||
))}
|
<Button
|
||||||
</nav>
|
variant="ghost"
|
||||||
|
color="red"
|
||||||
<div className={slots.actions()}>
|
size={3}
|
||||||
{identity == null
|
className="w-full justify-start"
|
||||||
? (
|
iconStart={<SignOutIcon />}
|
||||||
<Button
|
disabled={isSigningOut}
|
||||||
variant="solid"
|
onClick={() => {
|
||||||
color="neutral"
|
close();
|
||||||
highContrast
|
void signOut();
|
||||||
size={3}
|
}}
|
||||||
className="w-full"
|
>
|
||||||
iconStart={<LockSimpleIcon />}
|
{t("userMenu.signOut")}
|
||||||
onClick={() => {
|
</Button>
|
||||||
close();
|
</>
|
||||||
openSignIn({ continueTo: buildRequestAllContinueUrl() });
|
)}
|
||||||
}}
|
</DrawerFooter>
|
||||||
>
|
</DrawerPopup>
|
||||||
{t("topBar.getAccess")}
|
</Drawer>
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
: (
|
|
||||||
<>
|
|
||||||
{displayName
|
|
||||||
? (
|
|
||||||
<div className="flex flex-col gap-0.5 px-1 pb-2">
|
|
||||||
<Text size={2} weight="medium" color="neutral" highContrast className="truncate">
|
|
||||||
{displayName}
|
|
||||||
</Text>
|
|
||||||
<Text size={1} color="faint" className="truncate">
|
|
||||||
{identity.email}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
: null}
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
color={isSubscribed ? "green" : "gold"}
|
|
||||||
size={3}
|
|
||||||
className="w-full justify-start"
|
|
||||||
iconStart={isSubscribed ? <BellRingingIcon /> : <BellIcon />}
|
|
||||||
disabled={isUnsubscribing}
|
|
||||||
onClick={() => {
|
|
||||||
close();
|
|
||||||
if (isSubscribed) {
|
|
||||||
void unsubscribe();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
openSubscribe();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isSubscribed ? t("userMenu.subscribed") : t("userMenu.subscribe")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
color="red"
|
|
||||||
size={3}
|
|
||||||
className="w-full justify-start"
|
|
||||||
iconStart={<SignOutIcon />}
|
|
||||||
disabled={isSigningOut}
|
|
||||||
onClick={() => {
|
|
||||||
close();
|
|
||||||
void signOut();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("userMenu.signOut")}
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Drawer.Content>
|
|
||||||
</Drawer.Popup>
|
|
||||||
</Drawer.Viewport>
|
|
||||||
</Drawer.Portal>
|
|
||||||
</Drawer.Root>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,30 +43,7 @@ export const topBarUserMenuTrigger = tv({
|
|||||||
base: [
|
base: [
|
||||||
"flex h-8 items-center gap-2 rounded-full py-1 pr-2.5 pl-1",
|
"flex h-8 items-center gap-2 rounded-full py-1 pr-2.5 pl-1",
|
||||||
"cursor-pointer outline-none transition-colors select-none",
|
"cursor-pointer outline-none transition-colors select-none",
|
||||||
"hover:bg-sand-3 data-[popup-open]:bg-sand-3",
|
"hover:bg-sand-3 data-popup-open:bg-sand-3",
|
||||||
"focus-visible:ring-2 focus-visible:ring-sand-8 focus-visible:ring-offset-1 focus-visible:ring-offset-sand-1",
|
"focus-visible:ring-2 focus-visible:ring-sand-8 focus-visible:ring-offset-1 focus-visible:ring-offset-sand-1",
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Right-edge mobile navigation drawer (Base UI Drawer).
|
|
||||||
export const topBarMobileNav = tv({
|
|
||||||
slots: {
|
|
||||||
backdrop: [
|
|
||||||
"fixed inset-0 z-50 bg-sand-12/40",
|
|
||||||
"transition-opacity duration-200",
|
|
||||||
"data-starting-style:opacity-0 data-ending-style:opacity-0",
|
|
||||||
],
|
|
||||||
viewport: "fixed inset-0 z-50 flex justify-end",
|
|
||||||
popup: [
|
|
||||||
"relative flex h-full w-[min(20rem,100%)] flex-col bg-sand-1 shadow-6 outline-none",
|
|
||||||
"[transform:translateX(var(--drawer-swipe-movement-x,0px))]",
|
|
||||||
"transition-transform duration-200",
|
|
||||||
"data-starting-style:translate-x-full data-ending-style:translate-x-full",
|
|
||||||
],
|
|
||||||
content: "flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-4",
|
|
||||||
header: "flex items-center justify-between gap-3",
|
|
||||||
title: "text-4 font-medium text-sand-12",
|
|
||||||
nav: "flex flex-col gap-1",
|
|
||||||
actions: "mt-auto flex flex-col gap-2 border-t border-sand-a3 pt-4",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|||||||
124
packages/ui/src/v2/Drawer/Drawer.stories.tsx
Normal file
124
packages/ui/src/v2/Drawer/Drawer.stories.tsx
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
import { Button } from "../Button/Button";
|
||||||
|
import { Text } from "../typography/Text";
|
||||||
|
|
||||||
|
import { Drawer } from "./Drawer";
|
||||||
|
import { DrawerBody } from "./DrawerBody";
|
||||||
|
import { DrawerClose } from "./DrawerClose";
|
||||||
|
import { DrawerDescription } from "./DrawerDescription";
|
||||||
|
import { DrawerFooter } from "./DrawerFooter";
|
||||||
|
import { DrawerHeader } from "./DrawerHeader";
|
||||||
|
import { DrawerPopup } from "./DrawerPopup";
|
||||||
|
import { DrawerSkeleton } from "./DrawerSkeleton";
|
||||||
|
import { DrawerTitle } from "./DrawerTitle";
|
||||||
|
import { DrawerTrigger } from "./DrawerTrigger";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "v2/Drawer",
|
||||||
|
component: Drawer,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Default() {
|
||||||
|
return (
|
||||||
|
<Drawer swipeDirection="right">
|
||||||
|
<DrawerTrigger render={<Button variant="solid" color="neutral" highContrast>Open drawer</Button>} />
|
||||||
|
<DrawerPopup side="right">
|
||||||
|
<DrawerHeader>
|
||||||
|
<DrawerTitle>Menu</DrawerTitle>
|
||||||
|
<DrawerClose render={<Button variant="soft" color="neutral" highContrast>Close</Button>} />
|
||||||
|
</DrawerHeader>
|
||||||
|
<DrawerBody>
|
||||||
|
<Text size={2} color="neutral">
|
||||||
|
Drawer body content — navigation links, filters, or any other slot
|
||||||
|
the surrounding page composes.
|
||||||
|
</Text>
|
||||||
|
</DrawerBody>
|
||||||
|
<DrawerFooter>
|
||||||
|
<DrawerClose render={<Button variant="solid" color="neutral" highContrast className="w-full">Done</Button>} />
|
||||||
|
</DrawerFooter>
|
||||||
|
</DrawerPopup>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BottomSheet() {
|
||||||
|
return (
|
||||||
|
<Drawer swipeDirection="down">
|
||||||
|
<DrawerTrigger render={<Button variant="soft" color="neutral" highContrast>Open bottom sheet</Button>} />
|
||||||
|
<DrawerPopup side="bottom">
|
||||||
|
<DrawerHeader>
|
||||||
|
<DrawerTitle>Options</DrawerTitle>
|
||||||
|
<DrawerClose render={<Button variant="soft" color="neutral" highContrast>Close</Button>} />
|
||||||
|
</DrawerHeader>
|
||||||
|
<DrawerBody>
|
||||||
|
<DrawerDescription>
|
||||||
|
Swipe down to dismiss.
|
||||||
|
</DrawerDescription>
|
||||||
|
</DrawerBody>
|
||||||
|
<DrawerFooter>
|
||||||
|
<DrawerClose render={<Button variant="solid" color="neutral" highContrast className="w-full">Confirm</Button>} />
|
||||||
|
</DrawerFooter>
|
||||||
|
</DrawerPopup>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Controlled() {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-start gap-3">
|
||||||
|
<Button variant="soft" color="neutral" highContrast onClick={() => setOpen(true)}>
|
||||||
|
Open controlled drawer
|
||||||
|
</Button>
|
||||||
|
<Text size={1} color="faint">{open ? "open" : "closed"}</Text>
|
||||||
|
|
||||||
|
<Drawer open={open} onOpenChange={setOpen} swipeDirection="right">
|
||||||
|
<DrawerPopup side="right">
|
||||||
|
<DrawerHeader>
|
||||||
|
<DrawerTitle>Account</DrawerTitle>
|
||||||
|
</DrawerHeader>
|
||||||
|
<DrawerBody>
|
||||||
|
<Text size={2} color="neutral">
|
||||||
|
Controlled the same way as Base UI — `open` / `onOpenChange`.
|
||||||
|
</Text>
|
||||||
|
</DrawerBody>
|
||||||
|
<DrawerFooter>
|
||||||
|
<Button variant="solid" color="neutral" highContrast className="w-full" onClick={() => setOpen(false)}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</DrawerFooter>
|
||||||
|
</DrawerPopup>
|
||||||
|
</Drawer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Skeleton() {
|
||||||
|
return (
|
||||||
|
<div className="flex h-80 justify-end border border-sand-6 bg-sand-2">
|
||||||
|
<DrawerSkeleton />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
32
packages/ui/src/v2/Drawer/Drawer.tsx
Normal file
32
packages/ui/src/v2/Drawer/Drawer.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
import { Drawer as BaseDrawer } from "@base-ui/react/drawer";
|
||||||
|
import type { ComponentProps } from "react";
|
||||||
|
|
||||||
|
// Root of the edge drawer. Controlled the same way as Base UI's Drawer:
|
||||||
|
// `open` / `onOpenChange`, plus `swipeDirection` for dismiss gestures. Pair
|
||||||
|
// `swipeDirection` with `DrawerPopup`'s `side` (right↔right, left↔left,
|
||||||
|
// bottom↔down, top↔up). See contrib/claude/ui.md.
|
||||||
|
export type DrawerProps = ComponentProps<typeof BaseDrawer.Root>;
|
||||||
|
|
||||||
|
export function Drawer(props: DrawerProps) {
|
||||||
|
return <BaseDrawer.Root {...props} />;
|
||||||
|
}
|
||||||
31
packages/ui/src/v2/Drawer/DrawerBody.tsx
Normal file
31
packages/ui/src/v2/Drawer/DrawerBody.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
import type { ComponentProps } from "react";
|
||||||
|
|
||||||
|
import { drawer } from "./variants";
|
||||||
|
|
||||||
|
// Main content region between the header and footer.
|
||||||
|
export function DrawerBody(props: ComponentProps<"div">) {
|
||||||
|
const { className, ...rest } = props;
|
||||||
|
const { body } = drawer();
|
||||||
|
|
||||||
|
return <div className={body({ className })} {...rest} />;
|
||||||
|
}
|
||||||
30
packages/ui/src/v2/Drawer/DrawerClose.tsx
Normal file
30
packages/ui/src/v2/Drawer/DrawerClose.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
import { Drawer as BaseDrawer } from "@base-ui/react/drawer";
|
||||||
|
import type { ComponentProps } from "react";
|
||||||
|
|
||||||
|
// Closes the drawer. Pass `render` to reuse an existing control (e.g. an
|
||||||
|
// IconButton) as the close action, per Base UI's polymorphism.
|
||||||
|
export type DrawerCloseProps = ComponentProps<typeof BaseDrawer.Close>;
|
||||||
|
|
||||||
|
export function DrawerClose(props: DrawerCloseProps) {
|
||||||
|
return <BaseDrawer.Close {...props} />;
|
||||||
|
}
|
||||||
37
packages/ui/src/v2/Drawer/DrawerDescription.tsx
Normal file
37
packages/ui/src/v2/Drawer/DrawerDescription.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
import { Drawer as BaseDrawer } from "@base-ui/react/drawer";
|
||||||
|
import type { ComponentProps } from "react";
|
||||||
|
|
||||||
|
import { drawer } from "./variants";
|
||||||
|
|
||||||
|
// Accessible drawer description (wires `aria-describedby` via Base UI).
|
||||||
|
export type DrawerDescriptionProps
|
||||||
|
= Omit<ComponentProps<typeof BaseDrawer.Description>, "className"> & {
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function DrawerDescription(props: DrawerDescriptionProps) {
|
||||||
|
const { className, ...rest } = props;
|
||||||
|
const { description } = drawer();
|
||||||
|
|
||||||
|
return <BaseDrawer.Description className={description({ className })} {...rest} />;
|
||||||
|
}
|
||||||
31
packages/ui/src/v2/Drawer/DrawerFooter.tsx
Normal file
31
packages/ui/src/v2/Drawer/DrawerFooter.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
import type { ComponentProps } from "react";
|
||||||
|
|
||||||
|
import { drawer } from "./variants";
|
||||||
|
|
||||||
|
// Footer region pinned to the bottom of the drawer for primary actions.
|
||||||
|
export function DrawerFooter(props: ComponentProps<"div">) {
|
||||||
|
const { className, ...rest } = props;
|
||||||
|
const { footer } = drawer();
|
||||||
|
|
||||||
|
return <div className={footer({ className })} {...rest} />;
|
||||||
|
}
|
||||||
31
packages/ui/src/v2/Drawer/DrawerHeader.tsx
Normal file
31
packages/ui/src/v2/Drawer/DrawerHeader.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
import type { ComponentProps } from "react";
|
||||||
|
|
||||||
|
import { drawer } from "./variants";
|
||||||
|
|
||||||
|
// Header region grouping the title (and optional close control).
|
||||||
|
export function DrawerHeader(props: ComponentProps<"div">) {
|
||||||
|
const { className, ...rest } = props;
|
||||||
|
const { header } = drawer();
|
||||||
|
|
||||||
|
return <div className={header({ className })} {...rest} />;
|
||||||
|
}
|
||||||
52
packages/ui/src/v2/Drawer/DrawerPopup.tsx
Normal file
52
packages/ui/src/v2/Drawer/DrawerPopup.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
import { Drawer as BaseDrawer } from "@base-ui/react/drawer";
|
||||||
|
import type { ComponentProps } from "react";
|
||||||
|
import type { VariantProps } from "tailwind-variants/lite";
|
||||||
|
|
||||||
|
import { drawer } from "./variants";
|
||||||
|
|
||||||
|
export type DrawerPopupProps
|
||||||
|
= & Omit<ComponentProps<typeof BaseDrawer.Popup>, "className">
|
||||||
|
& VariantProps<typeof drawer>
|
||||||
|
& {
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Portal + dimmed backdrop + edge-aligned popup. Children compose the header /
|
||||||
|
// body / footer regions inside Base UI's Content (swipe-safe text selection).
|
||||||
|
export function DrawerPopup(props: DrawerPopupProps) {
|
||||||
|
const { className, children, side, ...popupProps } = props;
|
||||||
|
const slots = drawer({ side });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseDrawer.Portal>
|
||||||
|
<BaseDrawer.Backdrop className={slots.backdrop()} />
|
||||||
|
<BaseDrawer.Viewport className={slots.viewport()}>
|
||||||
|
<BaseDrawer.Popup className={slots.popup({ className })} {...popupProps}>
|
||||||
|
<BaseDrawer.Content className={slots.content()}>
|
||||||
|
{children}
|
||||||
|
</BaseDrawer.Content>
|
||||||
|
</BaseDrawer.Popup>
|
||||||
|
</BaseDrawer.Viewport>
|
||||||
|
</BaseDrawer.Portal>
|
||||||
|
);
|
||||||
|
}
|
||||||
46
packages/ui/src/v2/Drawer/DrawerSkeleton.tsx
Normal file
46
packages/ui/src/v2/Drawer/DrawerSkeleton.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
import { TextSkeleton } from "../typography/TextSkeleton";
|
||||||
|
|
||||||
|
import { drawer, drawerSkeleton } from "./variants";
|
||||||
|
|
||||||
|
// Loading placeholder matching the drawer frame (header + body + footer),
|
||||||
|
// importing only variants and skeleton primitives — never Base UI.
|
||||||
|
export function DrawerSkeleton() {
|
||||||
|
const { header, body, footer } = drawer();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={drawerSkeleton()} aria-hidden>
|
||||||
|
<div className={header()}>
|
||||||
|
<TextSkeleton size={4} className="w-24" />
|
||||||
|
<TextSkeleton size={2} className="w-8" />
|
||||||
|
</div>
|
||||||
|
<div className={body()}>
|
||||||
|
<TextSkeleton size={2} className="w-full" />
|
||||||
|
<TextSkeleton size={2} className="w-3/4" />
|
||||||
|
<TextSkeleton size={2} className="w-5/6" />
|
||||||
|
</div>
|
||||||
|
<div className={footer()}>
|
||||||
|
<TextSkeleton size={2} className="w-full" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
36
packages/ui/src/v2/Drawer/DrawerTitle.tsx
Normal file
36
packages/ui/src/v2/Drawer/DrawerTitle.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
import { Drawer as BaseDrawer } from "@base-ui/react/drawer";
|
||||||
|
import type { ComponentProps } from "react";
|
||||||
|
|
||||||
|
import { drawer } from "./variants";
|
||||||
|
|
||||||
|
// Accessible drawer title (wires `aria-labelledby` via Base UI).
|
||||||
|
export type DrawerTitleProps = Omit<ComponentProps<typeof BaseDrawer.Title>, "className"> & {
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function DrawerTitle(props: DrawerTitleProps) {
|
||||||
|
const { className, ...rest } = props;
|
||||||
|
const { title } = drawer();
|
||||||
|
|
||||||
|
return <BaseDrawer.Title className={title({ className })} {...rest} />;
|
||||||
|
}
|
||||||
30
packages/ui/src/v2/Drawer/DrawerTrigger.tsx
Normal file
30
packages/ui/src/v2/Drawer/DrawerTrigger.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
import { Drawer as BaseDrawer } from "@base-ui/react/drawer";
|
||||||
|
import type { ComponentProps } from "react";
|
||||||
|
|
||||||
|
// Opens the drawer. Pass `render` to use an existing control (e.g. an
|
||||||
|
// IconButton) as the trigger, per Base UI's polymorphism.
|
||||||
|
export type DrawerTriggerProps = ComponentProps<typeof BaseDrawer.Trigger>;
|
||||||
|
|
||||||
|
export function DrawerTrigger(props: DrawerTriggerProps) {
|
||||||
|
return <BaseDrawer.Trigger {...props} />;
|
||||||
|
}
|
||||||
91
packages/ui/src/v2/Drawer/variants.ts
Normal file
91
packages/ui/src/v2/Drawer/variants.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
import { tv } from "tailwind-variants/lite";
|
||||||
|
|
||||||
|
// Edge drawer over Base UI. Positioning lives in CSS (`side`); pass the matching
|
||||||
|
// `swipeDirection` on `Drawer` (right↔right, left↔left, bottom↔down, top↔up).
|
||||||
|
export const drawer = tv({
|
||||||
|
slots: {
|
||||||
|
backdrop: [
|
||||||
|
"fixed inset-0 z-50 bg-sand-12/40",
|
||||||
|
"transition-opacity duration-200",
|
||||||
|
"data-starting-style:opacity-0 data-ending-style:opacity-0",
|
||||||
|
],
|
||||||
|
viewport: "fixed inset-0 z-50 flex",
|
||||||
|
popup: [
|
||||||
|
"relative flex flex-col bg-sand-1 shadow-6 outline-none",
|
||||||
|
"transition-transform duration-200",
|
||||||
|
],
|
||||||
|
content: "flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-4",
|
||||||
|
header: "flex items-center justify-between gap-3",
|
||||||
|
title: "text-4 font-medium text-sand-12",
|
||||||
|
description: "text-2 text-sand-11",
|
||||||
|
body: "flex min-h-0 flex-1 flex-col gap-1",
|
||||||
|
footer: "mt-auto flex flex-col gap-2 border-t border-sand-a3 pt-4",
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
side: {
|
||||||
|
right: {
|
||||||
|
viewport: "justify-end",
|
||||||
|
popup: [
|
||||||
|
"h-full w-[min(20rem,100%)]",
|
||||||
|
"transform-[translateX(var(--drawer-swipe-movement-x,0px))]",
|
||||||
|
"data-starting-style:translate-x-full data-ending-style:translate-x-full",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
left: {
|
||||||
|
viewport: "justify-start",
|
||||||
|
popup: [
|
||||||
|
"h-full w-[min(20rem,100%)]",
|
||||||
|
"transform-[translateX(var(--drawer-swipe-movement-x,0px))]",
|
||||||
|
"data-starting-style:-translate-x-full data-ending-style:-translate-x-full",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
bottom: {
|
||||||
|
viewport: "items-end justify-center",
|
||||||
|
popup: [
|
||||||
|
"w-full max-h-[min(90dvh,100%)] rounded-t-5",
|
||||||
|
"transform-[translateY(var(--drawer-swipe-movement-y,0px))]",
|
||||||
|
"data-starting-style:translate-y-full data-ending-style:translate-y-full",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
top: {
|
||||||
|
viewport: "items-start justify-center",
|
||||||
|
popup: [
|
||||||
|
"w-full max-h-[min(90dvh,100%)] rounded-b-5",
|
||||||
|
"transform-[translateY(var(--drawer-swipe-movement-y,0px))]",
|
||||||
|
"data-starting-style:-translate-y-full data-ending-style:-translate-y-full",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
side: "right",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Static frame matching the drawer popup, without Base UI positioning, so a
|
||||||
|
// placeholder can render before the interactive drawer loads.
|
||||||
|
export const drawerSkeleton = tv({
|
||||||
|
base: [
|
||||||
|
"flex h-full w-[min(20rem,100%)] flex-col gap-4 bg-sand-1 p-4 shadow-6",
|
||||||
|
],
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user