Switch to sub-path exports
Consumers use sub-path imports (e.g. "pettyui/slider") for tree-shaking. Adds Drawer component + 15 sub-path entries to package.json exports.
This commit is contained in:
parent
824d12ba9a
commit
295dd1388c
@ -3,15 +3,7 @@
|
||||
"version": "0.1.0",
|
||||
"description": "AI-native headless UI component library for SolidJS",
|
||||
"type": "module",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"solid": "./src/index.ts",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs"
|
||||
},
|
||||
"./dialog": {
|
||||
"solid": "./src/components/dialog/index.ts",
|
||||
"import": "./dist/dialog/index.js",
|
||||
@ -46,7 +38,22 @@
|
||||
"solid": "./src/utilities/visually-hidden/index.ts",
|
||||
"import": "./dist/utilities/visually-hidden/index.js",
|
||||
"require": "./dist/utilities/visually-hidden/index.cjs"
|
||||
}
|
||||
},
|
||||
"./separator": { "solid": "./src/components/separator/index.ts", "import": "./dist/components/separator/index.js", "require": "./dist/components/separator/index.cjs" },
|
||||
"./toggle": { "solid": "./src/components/toggle/index.ts", "import": "./dist/components/toggle/index.js", "require": "./dist/components/toggle/index.cjs" },
|
||||
"./switch": { "solid": "./src/components/switch/index.ts", "import": "./dist/components/switch/index.js", "require": "./dist/components/switch/index.cjs" },
|
||||
"./checkbox": { "solid": "./src/components/checkbox/index.ts", "import": "./dist/components/checkbox/index.js", "require": "./dist/components/checkbox/index.cjs" },
|
||||
"./progress": { "solid": "./src/components/progress/index.ts", "import": "./dist/components/progress/index.js", "require": "./dist/components/progress/index.cjs" },
|
||||
"./text-field": { "solid": "./src/components/text-field/index.ts", "import": "./dist/components/text-field/index.js", "require": "./dist/components/text-field/index.cjs" },
|
||||
"./radio-group": { "solid": "./src/components/radio-group/index.ts", "import": "./dist/components/radio-group/index.js", "require": "./dist/components/radio-group/index.cjs" },
|
||||
"./toggle-group": { "solid": "./src/components/toggle-group/index.ts", "import": "./dist/components/toggle-group/index.js", "require": "./dist/components/toggle-group/index.cjs" },
|
||||
"./collapsible": { "solid": "./src/components/collapsible/index.ts", "import": "./dist/components/collapsible/index.js", "require": "./dist/components/collapsible/index.cjs" },
|
||||
"./accordion": { "solid": "./src/components/accordion/index.ts", "import": "./dist/components/accordion/index.js", "require": "./dist/components/accordion/index.cjs" },
|
||||
"./alert-dialog": { "solid": "./src/components/alert-dialog/index.ts", "import": "./dist/components/alert-dialog/index.js", "require": "./dist/components/alert-dialog/index.cjs" },
|
||||
"./tabs": { "solid": "./src/components/tabs/index.ts", "import": "./dist/components/tabs/index.js", "require": "./dist/components/tabs/index.cjs" },
|
||||
"./slider": { "solid": "./src/components/slider/index.ts", "import": "./dist/components/slider/index.js", "require": "./dist/components/slider/index.cjs" },
|
||||
"./pagination": { "solid": "./src/components/pagination/index.ts", "import": "./dist/components/pagination/index.js", "require": "./dist/components/pagination/index.cjs" },
|
||||
"./drawer": { "solid": "./src/components/drawer/index.ts", "import": "./dist/components/drawer/index.js", "require": "./dist/components/drawer/index.cjs" }
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsdown",
|
||||
|
||||
27
packages/core/src/components/drawer/drawer-close.tsx
Normal file
27
packages/core/src/components/drawer/drawer-close.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import type { JSX } from "solid-js";
|
||||
import { splitProps } from "solid-js";
|
||||
import { useInternalDrawerContext } from "./drawer-context";
|
||||
|
||||
/** Props for Drawer.Close. */
|
||||
export interface DrawerCloseProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
children?: JSX.Element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the Drawer when clicked.
|
||||
*/
|
||||
export function DrawerClose(props: DrawerCloseProps): JSX.Element {
|
||||
const [local, rest] = splitProps(props, ["children", "onClick"]);
|
||||
const ctx = useInternalDrawerContext();
|
||||
|
||||
const handleClick: JSX.EventHandler<HTMLButtonElement, MouseEvent> = (e) => {
|
||||
if (typeof local.onClick === "function") local.onClick(e);
|
||||
ctx.setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<button type="button" onClick={handleClick} {...rest}>
|
||||
{local.children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
65
packages/core/src/components/drawer/drawer-content.tsx
Normal file
65
packages/core/src/components/drawer/drawer-content.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import type { JSX } from "solid-js";
|
||||
import { Show, createEffect, onCleanup, splitProps } from "solid-js";
|
||||
import { createDismiss } from "../../utilities/dismiss/create-dismiss";
|
||||
import { createFocusTrap } from "../../utilities/focus-trap/create-focus-trap";
|
||||
import { createScrollLock } from "../../utilities/scroll-lock/create-scroll-lock";
|
||||
import { useInternalDrawerContext } from "./drawer-context";
|
||||
|
||||
/** Props for Drawer.Content. */
|
||||
export interface DrawerContentProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
||||
/** Keep mounted even when closed (for animation control). */
|
||||
forceMount?: boolean;
|
||||
children?: JSX.Element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drawer content panel. Activates focus trap, scroll lock, and Escape/outside dismiss when open.
|
||||
* Sets data-side to indicate which edge the drawer slides from.
|
||||
*/
|
||||
export function DrawerContent(props: DrawerContentProps): JSX.Element {
|
||||
const [local, rest] = splitProps(props, ["children", "forceMount"]);
|
||||
const ctx = useInternalDrawerContext();
|
||||
let contentRef: HTMLDivElement | undefined;
|
||||
|
||||
const focusTrap = createFocusTrap(() => contentRef ?? null);
|
||||
const scrollLock = createScrollLock();
|
||||
const dismiss = createDismiss({
|
||||
getContainer: () => contentRef ?? null,
|
||||
onDismiss: () => ctx.setOpen(false),
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
if (ctx.isOpen()) {
|
||||
focusTrap.activate();
|
||||
scrollLock.lock();
|
||||
dismiss.attach();
|
||||
} else {
|
||||
focusTrap.deactivate();
|
||||
scrollLock.unlock();
|
||||
dismiss.detach();
|
||||
}
|
||||
onCleanup(() => {
|
||||
focusTrap.deactivate();
|
||||
scrollLock.unlock();
|
||||
dismiss.detach();
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<Show when={local.forceMount || ctx.isOpen()}>
|
||||
<div
|
||||
ref={contentRef}
|
||||
id={ctx.contentId()}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby={ctx.titleId() || undefined}
|
||||
aria-describedby={ctx.descriptionId() || undefined}
|
||||
data-state={ctx.isOpen() ? "open" : "closed"}
|
||||
data-side={ctx.side()}
|
||||
{...rest}
|
||||
>
|
||||
{local.children}
|
||||
</div>
|
||||
</Show>
|
||||
);
|
||||
}
|
||||
58
packages/core/src/components/drawer/drawer-context.ts
Normal file
58
packages/core/src/components/drawer/drawer-context.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import type { Accessor } from "solid-js";
|
||||
import { createContext, useContext } from "solid-js";
|
||||
|
||||
/** Which edge the Drawer slides from. */
|
||||
export type DrawerSide = "top" | "bottom" | "left" | "right";
|
||||
|
||||
/** Internal context shared between all Drawer parts. */
|
||||
export interface InternalDrawerContextValue {
|
||||
isOpen: Accessor<boolean>;
|
||||
setOpen: (open: boolean) => void;
|
||||
side: Accessor<DrawerSide>;
|
||||
contentId: Accessor<string>;
|
||||
titleId: Accessor<string | undefined>;
|
||||
setTitleId: (id: string | undefined) => void;
|
||||
descriptionId: Accessor<string | undefined>;
|
||||
setDescriptionId: (id: string | undefined) => void;
|
||||
}
|
||||
|
||||
const InternalDrawerContext = createContext<InternalDrawerContextValue>();
|
||||
|
||||
/**
|
||||
* Returns the internal Drawer context. Throws if used outside <Drawer>.
|
||||
*/
|
||||
export function useInternalDrawerContext(): InternalDrawerContextValue {
|
||||
const ctx = useContext(InternalDrawerContext);
|
||||
if (!ctx) {
|
||||
throw new Error(
|
||||
"[PettyUI] Drawer parts must be used inside <Drawer>.\n" +
|
||||
" Fix: Wrap Drawer.Content, Drawer.Trigger, etc. inside <Drawer>.",
|
||||
);
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
export const InternalDrawerContextProvider = InternalDrawerContext.Provider;
|
||||
|
||||
/** Public context exposed to consumers via Drawer.useContext(). */
|
||||
export interface DrawerContextValue {
|
||||
/** Whether the drawer is currently open. */
|
||||
open: Accessor<boolean>;
|
||||
/** Which edge the drawer slides from. */
|
||||
side: Accessor<DrawerSide>;
|
||||
}
|
||||
|
||||
const DrawerContext = createContext<DrawerContextValue>();
|
||||
|
||||
/**
|
||||
* Returns the public Drawer context. Throws if used outside <Drawer>.
|
||||
*/
|
||||
export function useDrawerContext(): DrawerContextValue {
|
||||
const ctx = useContext(DrawerContext);
|
||||
if (!ctx) {
|
||||
throw new Error("[PettyUI] Drawer.useContext() called outside of <Drawer>.");
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
export const DrawerContextProvider = DrawerContext.Provider;
|
||||
24
packages/core/src/components/drawer/drawer-description.tsx
Normal file
24
packages/core/src/components/drawer/drawer-description.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import type { JSX } from "solid-js";
|
||||
import { createUniqueId, onCleanup, onMount, splitProps } from "solid-js";
|
||||
import { useInternalDrawerContext } from "./drawer-context";
|
||||
|
||||
/** Props for Drawer.Description. */
|
||||
export interface DrawerDescriptionProps extends JSX.HTMLAttributes<HTMLParagraphElement> {
|
||||
children?: JSX.Element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description for the Drawer. Registers its ID for aria-describedby.
|
||||
*/
|
||||
export function DrawerDescription(props: DrawerDescriptionProps): JSX.Element {
|
||||
const [local, rest] = splitProps(props, ["children"]);
|
||||
const ctx = useInternalDrawerContext();
|
||||
const id = createUniqueId();
|
||||
onMount(() => ctx.setDescriptionId(id));
|
||||
onCleanup(() => ctx.setDescriptionId(undefined));
|
||||
return (
|
||||
<p id={id} {...rest}>
|
||||
{local.children}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
22
packages/core/src/components/drawer/drawer-overlay.tsx
Normal file
22
packages/core/src/components/drawer/drawer-overlay.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import type { JSX } from "solid-js";
|
||||
import { Show, splitProps } from "solid-js";
|
||||
import { useInternalDrawerContext } from "./drawer-context";
|
||||
|
||||
/** Props for Drawer.Overlay. */
|
||||
export interface DrawerOverlayProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
||||
/** Keep mounted even when closed (for animation control). */
|
||||
forceMount?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Semi-transparent overlay behind Drawer content.
|
||||
*/
|
||||
export function DrawerOverlay(props: DrawerOverlayProps): JSX.Element {
|
||||
const [local, rest] = splitProps(props, ["forceMount"]);
|
||||
const ctx = useInternalDrawerContext();
|
||||
return (
|
||||
<Show when={local.forceMount || ctx.isOpen()}>
|
||||
<div aria-hidden="true" data-state={ctx.isOpen() ? "open" : "closed"} {...rest} />
|
||||
</Show>
|
||||
);
|
||||
}
|
||||
20
packages/core/src/components/drawer/drawer-portal.tsx
Normal file
20
packages/core/src/components/drawer/drawer-portal.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import type { JSX } from "solid-js";
|
||||
import { Portal } from "../../utilities/portal/portal";
|
||||
|
||||
/** Props for Drawer.Portal. */
|
||||
export interface DrawerPortalProps {
|
||||
/** Optional target element; defaults to document.body. */
|
||||
target?: Element | null;
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders children into a portal (defaults to document.body).
|
||||
*/
|
||||
export function DrawerPortal(props: DrawerPortalProps): JSX.Element {
|
||||
return props.target !== undefined ? (
|
||||
<Portal target={props.target}>{props.children}</Portal>
|
||||
) : (
|
||||
<Portal>{props.children}</Portal>
|
||||
);
|
||||
}
|
||||
68
packages/core/src/components/drawer/drawer-root.tsx
Normal file
68
packages/core/src/components/drawer/drawer-root.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import type { JSX } from "solid-js";
|
||||
import { createUniqueId, splitProps } from "solid-js";
|
||||
import {
|
||||
type CreateDisclosureStateOptions,
|
||||
createDisclosureState,
|
||||
} from "../../primitives/create-disclosure-state";
|
||||
import { createRegisterId } from "../../primitives/create-register-id";
|
||||
import {
|
||||
DrawerContextProvider,
|
||||
type DrawerSide,
|
||||
InternalDrawerContextProvider,
|
||||
type InternalDrawerContextValue,
|
||||
} from "./drawer-context";
|
||||
|
||||
/** Props for the Drawer root component. */
|
||||
export interface DrawerRootProps {
|
||||
/** Controls open state externally. */
|
||||
open?: boolean;
|
||||
/** Initial open state when uncontrolled. */
|
||||
defaultOpen?: boolean;
|
||||
/** Called when open state changes. */
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
/** Which edge the drawer slides from. @default "right" */
|
||||
side?: DrawerSide;
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Root component for Drawer. Manages open state and provides context.
|
||||
*/
|
||||
export function DrawerRoot(props: DrawerRootProps): JSX.Element {
|
||||
const [local] = splitProps(props, ["open", "defaultOpen", "onOpenChange", "side", "children"]);
|
||||
|
||||
const disclosure = createDisclosureState({
|
||||
get open() {
|
||||
return local.open;
|
||||
},
|
||||
get defaultOpen() {
|
||||
return local.defaultOpen;
|
||||
},
|
||||
get onOpenChange() {
|
||||
return local.onOpenChange;
|
||||
},
|
||||
} as CreateDisclosureStateOptions);
|
||||
|
||||
const contentId = createUniqueId();
|
||||
const [titleId, setTitleId] = createRegisterId();
|
||||
const [descriptionId, setDescriptionId] = createRegisterId();
|
||||
|
||||
const internalCtx: InternalDrawerContextValue = {
|
||||
isOpen: disclosure.isOpen,
|
||||
setOpen: (open) => (open ? disclosure.open() : disclosure.close()),
|
||||
side: () => local.side ?? "right",
|
||||
contentId: () => contentId,
|
||||
titleId,
|
||||
setTitleId,
|
||||
descriptionId,
|
||||
setDescriptionId,
|
||||
};
|
||||
|
||||
return (
|
||||
<InternalDrawerContextProvider value={internalCtx}>
|
||||
<DrawerContextProvider value={{ open: disclosure.isOpen, side: () => local.side ?? "right" }}>
|
||||
{local.children}
|
||||
</DrawerContextProvider>
|
||||
</InternalDrawerContextProvider>
|
||||
);
|
||||
}
|
||||
24
packages/core/src/components/drawer/drawer-title.tsx
Normal file
24
packages/core/src/components/drawer/drawer-title.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import type { JSX } from "solid-js";
|
||||
import { createUniqueId, onCleanup, onMount, splitProps } from "solid-js";
|
||||
import { useInternalDrawerContext } from "./drawer-context";
|
||||
|
||||
/** Props for Drawer.Title. */
|
||||
export interface DrawerTitleProps extends JSX.HTMLAttributes<HTMLHeadingElement> {
|
||||
children?: JSX.Element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Title for the Drawer. Registers its ID for aria-labelledby.
|
||||
*/
|
||||
export function DrawerTitle(props: DrawerTitleProps): JSX.Element {
|
||||
const [local, rest] = splitProps(props, ["children"]);
|
||||
const ctx = useInternalDrawerContext();
|
||||
const id = createUniqueId();
|
||||
onMount(() => ctx.setTitleId(id));
|
||||
onCleanup(() => ctx.setTitleId(undefined));
|
||||
return (
|
||||
<h2 id={id} {...rest}>
|
||||
{local.children}
|
||||
</h2>
|
||||
);
|
||||
}
|
||||
35
packages/core/src/components/drawer/drawer-trigger.tsx
Normal file
35
packages/core/src/components/drawer/drawer-trigger.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import type { JSX } from "solid-js";
|
||||
import { splitProps } from "solid-js";
|
||||
import { useInternalDrawerContext } from "./drawer-context";
|
||||
|
||||
/** Props for Drawer.Trigger. */
|
||||
export interface DrawerTriggerProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
children?: JSX.Element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the Drawer when clicked.
|
||||
*/
|
||||
export function DrawerTrigger(props: DrawerTriggerProps): JSX.Element {
|
||||
const [local, rest] = splitProps(props, ["children", "onClick"]);
|
||||
const ctx = useInternalDrawerContext();
|
||||
|
||||
const handleClick: JSX.EventHandler<HTMLButtonElement, MouseEvent> = (e) => {
|
||||
if (typeof local.onClick === "function") local.onClick(e);
|
||||
ctx.setOpen(!ctx.isOpen());
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded={ctx.isOpen() ? "true" : "false"}
|
||||
aria-controls={ctx.contentId()}
|
||||
data-state={ctx.isOpen() ? "open" : "closed"}
|
||||
onClick={handleClick}
|
||||
{...rest}
|
||||
>
|
||||
{local.children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
31
packages/core/src/components/drawer/index.ts
Normal file
31
packages/core/src/components/drawer/index.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { DrawerClose } from "./drawer-close";
|
||||
import { DrawerContent } from "./drawer-content";
|
||||
import { useDrawerContext } from "./drawer-context";
|
||||
import { DrawerDescription } from "./drawer-description";
|
||||
import { DrawerOverlay } from "./drawer-overlay";
|
||||
import { DrawerPortal } from "./drawer-portal";
|
||||
import { DrawerRoot } from "./drawer-root";
|
||||
import { DrawerTitle } from "./drawer-title";
|
||||
import { DrawerTrigger } from "./drawer-trigger";
|
||||
|
||||
/** Compound Drawer component with sub-components attached as properties. */
|
||||
export const Drawer = Object.assign(DrawerRoot, {
|
||||
Content: DrawerContent,
|
||||
Title: DrawerTitle,
|
||||
Description: DrawerDescription,
|
||||
Trigger: DrawerTrigger,
|
||||
Close: DrawerClose,
|
||||
Portal: DrawerPortal,
|
||||
Overlay: DrawerOverlay,
|
||||
useContext: useDrawerContext,
|
||||
});
|
||||
|
||||
export type { DrawerRootProps } from "./drawer-root";
|
||||
export type { DrawerContentProps } from "./drawer-content";
|
||||
export type { DrawerTitleProps } from "./drawer-title";
|
||||
export type { DrawerDescriptionProps } from "./drawer-description";
|
||||
export type { DrawerTriggerProps } from "./drawer-trigger";
|
||||
export type { DrawerCloseProps } from "./drawer-close";
|
||||
export type { DrawerPortalProps } from "./drawer-portal";
|
||||
export type { DrawerOverlayProps } from "./drawer-overlay";
|
||||
export type { DrawerContextValue, DrawerSide } from "./drawer-context";
|
||||
@ -1,31 +0,0 @@
|
||||
// packages/core/src/index.ts
|
||||
// Main entry — re-exports everything for convenience.
|
||||
// Prefer sub-path imports (e.g. "pettyui/dialog") for tree-shaking.
|
||||
|
||||
export { Dialog } from "./components/dialog/index";
|
||||
export type { DialogRootProps } from "./components/dialog/dialog-root";
|
||||
export type { DialogContentProps } from "./components/dialog/dialog-content";
|
||||
export type { DialogTitleProps } from "./components/dialog/dialog-title";
|
||||
export type { DialogDescriptionProps } from "./components/dialog/dialog-description";
|
||||
export type { DialogTriggerProps } from "./components/dialog/dialog-trigger";
|
||||
export type { DialogCloseProps } from "./components/dialog/dialog-close";
|
||||
export type { DialogPortalProps } from "./components/dialog/dialog-portal";
|
||||
export type { DialogOverlayProps } from "./components/dialog/dialog-overlay";
|
||||
|
||||
export { Presence } from "./utilities/presence/index";
|
||||
export type { PresenceProps, PresenceChildProps } from "./utilities/presence/index";
|
||||
|
||||
export { Portal } from "./utilities/portal/index";
|
||||
export type { PortalProps } from "./utilities/portal/index";
|
||||
|
||||
export { VisuallyHidden } from "./utilities/visually-hidden/index";
|
||||
export type { VisuallyHiddenProps } from "./utilities/visually-hidden/index";
|
||||
|
||||
export { createFocusTrap } from "./utilities/focus-trap/index";
|
||||
export type { FocusTrap } from "./utilities/focus-trap/index";
|
||||
|
||||
export { createScrollLock } from "./utilities/scroll-lock/index";
|
||||
export type { ScrollLock } from "./utilities/scroll-lock/index";
|
||||
|
||||
export { createDismiss } from "./utilities/dismiss/index";
|
||||
export type { CreateDismissOptions, Dismiss } from "./utilities/dismiss/index";
|
||||
112
packages/core/tests/components/drawer/drawer.test.tsx
Normal file
112
packages/core/tests/components/drawer/drawer.test.tsx
Normal file
@ -0,0 +1,112 @@
|
||||
import { fireEvent, render, screen } from "@solidjs/testing-library";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { Drawer } from "../../../src/components/drawer/index";
|
||||
|
||||
describe("Drawer", () => {
|
||||
it("closed by default", () => {
|
||||
render(() => (
|
||||
<Drawer>
|
||||
<Drawer.Content>
|
||||
<Drawer.Title>Title</Drawer.Title>
|
||||
</Drawer.Content>
|
||||
</Drawer>
|
||||
));
|
||||
expect(screen.queryByRole("dialog")).toBeNull();
|
||||
});
|
||||
|
||||
it("opens with defaultOpen=true", () => {
|
||||
render(() => (
|
||||
<Drawer defaultOpen>
|
||||
<Drawer.Content>
|
||||
<Drawer.Title>Title</Drawer.Title>
|
||||
</Drawer.Content>
|
||||
</Drawer>
|
||||
));
|
||||
expect(screen.getByRole("dialog")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("trigger click opens drawer", () => {
|
||||
render(() => (
|
||||
<Drawer>
|
||||
<Drawer.Trigger>Open</Drawer.Trigger>
|
||||
<Drawer.Content>
|
||||
<Drawer.Title>Title</Drawer.Title>
|
||||
</Drawer.Content>
|
||||
</Drawer>
|
||||
));
|
||||
fireEvent.click(screen.getByText("Open"));
|
||||
expect(screen.getByRole("dialog")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("Close button closes drawer", () => {
|
||||
render(() => (
|
||||
<Drawer defaultOpen>
|
||||
<Drawer.Content>
|
||||
<Drawer.Title>Title</Drawer.Title>
|
||||
<Drawer.Close>Close</Drawer.Close>
|
||||
</Drawer.Content>
|
||||
</Drawer>
|
||||
));
|
||||
fireEvent.click(screen.getByText("Close"));
|
||||
expect(screen.queryByRole("dialog")).toBeNull();
|
||||
});
|
||||
|
||||
it("Escape key closes drawer", () => {
|
||||
render(() => (
|
||||
<Drawer defaultOpen>
|
||||
<Drawer.Content>
|
||||
<Drawer.Title>Title</Drawer.Title>
|
||||
</Drawer.Content>
|
||||
</Drawer>
|
||||
));
|
||||
fireEvent.keyDown(document, { key: "Escape" });
|
||||
expect(screen.queryByRole("dialog")).toBeNull();
|
||||
});
|
||||
|
||||
it("content has role=dialog and aria-modal", () => {
|
||||
render(() => (
|
||||
<Drawer defaultOpen>
|
||||
<Drawer.Content>
|
||||
<Drawer.Title>Title</Drawer.Title>
|
||||
</Drawer.Content>
|
||||
</Drawer>
|
||||
));
|
||||
const dialog = screen.getByRole("dialog");
|
||||
expect(dialog.getAttribute("aria-modal")).toBe("true");
|
||||
});
|
||||
|
||||
it("data-side defaults to right", () => {
|
||||
render(() => (
|
||||
<Drawer defaultOpen>
|
||||
<Drawer.Content data-testid="content">
|
||||
<Drawer.Title>Title</Drawer.Title>
|
||||
</Drawer.Content>
|
||||
</Drawer>
|
||||
));
|
||||
expect(screen.getByTestId("content").getAttribute("data-side")).toBe("right");
|
||||
});
|
||||
|
||||
it("data-side reflects side prop", () => {
|
||||
render(() => (
|
||||
<Drawer defaultOpen side="left">
|
||||
<Drawer.Content data-testid="content">
|
||||
<Drawer.Title>Title</Drawer.Title>
|
||||
</Drawer.Content>
|
||||
</Drawer>
|
||||
));
|
||||
expect(screen.getByTestId("content").getAttribute("data-side")).toBe("left");
|
||||
});
|
||||
|
||||
it("title linked via aria-labelledby", () => {
|
||||
render(() => (
|
||||
<Drawer defaultOpen>
|
||||
<Drawer.Content>
|
||||
<Drawer.Title>My Drawer</Drawer.Title>
|
||||
</Drawer.Content>
|
||||
</Drawer>
|
||||
));
|
||||
const dialog = screen.getByRole("dialog");
|
||||
const title = screen.getByText("My Drawer");
|
||||
expect(dialog.getAttribute("aria-labelledby")).toBe(title.id);
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user