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",
|
"version": "0.1.0",
|
||||||
"description": "AI-native headless UI component library for SolidJS",
|
"description": "AI-native headless UI component library for SolidJS",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.cjs",
|
|
||||||
"module": "./dist/index.js",
|
|
||||||
"types": "./dist/index.d.ts",
|
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
|
||||||
"solid": "./src/index.ts",
|
|
||||||
"import": "./dist/index.js",
|
|
||||||
"require": "./dist/index.cjs"
|
|
||||||
},
|
|
||||||
"./dialog": {
|
"./dialog": {
|
||||||
"solid": "./src/components/dialog/index.ts",
|
"solid": "./src/components/dialog/index.ts",
|
||||||
"import": "./dist/dialog/index.js",
|
"import": "./dist/dialog/index.js",
|
||||||
@ -46,7 +38,22 @@
|
|||||||
"solid": "./src/utilities/visually-hidden/index.ts",
|
"solid": "./src/utilities/visually-hidden/index.ts",
|
||||||
"import": "./dist/utilities/visually-hidden/index.js",
|
"import": "./dist/utilities/visually-hidden/index.js",
|
||||||
"require": "./dist/utilities/visually-hidden/index.cjs"
|
"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": {
|
"scripts": {
|
||||||
"build": "tsdown",
|
"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