一键导入
implement-modus-modal-with-refs
Create Modus modals using the forwardRef + useImperativeHandle pattern for programmatic control
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Create Modus modals using the forwardRef + useImperativeHandle pattern for programmatic control
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Scaffold form components with proper Modus input integration, event handling, validation, and checkbox bug handling
Scaffold a new Modus wrapper component following established SolidJS patterns with proper TypeScript interfaces, event handling, and cleanup
Debug and fix common event handling problems with Modus web components
Apply the critical checkbox value inversion workaround when working with ModusCheckbox components
Create Modus modals using the callback ref pattern for programmatic control
Help with correct Modus icon usage patterns including naming conventions, sizing, and accessibility
| name | implement-modus-modal-with-refs |
| description | Create Modus modals using the forwardRef + useImperativeHandle pattern for programmatic control |
Create Modus modal components using the forwardRef + useImperativeHandle pattern for programmatic control.
Use this skill when:
Modus modals require:
forwardRef to expose methods to parent componentsuseImperativeHandle to define the API (openModal, closeModal)querySelector("dialog") to access the native dialog elementSee src/components/ModusModal.tsx for the complete implementation.
import { ModusWcModal } from "@trimble-oss/moduswebcomponents-react";
import type { ReactNode } from "react";
import { useRef, useEffect, forwardRef, useImperativeHandle } from "react";
/**
* Props for the ModusModal component.
*/
interface ModusModalProps {
/** A unique identifier for the modal. */
modalId: string;
/** The ARIA label for the modal. */
ariaLabel?: string;
/** The type of backdrop for the modal. */
backdrop?: 'default' | 'static';
/** The position of the modal. */
position?: 'top' | 'center' | 'bottom';
/** Whether the modal should be fullscreen. */
fullscreen?: boolean;
/** Whether to show the fullscreen toggle button. */
showFullscreenToggle?: boolean;
/** Whether to show the close button. */
showClose?: boolean;
/** A custom CSS class to apply to the modal. */
customClass?: string;
/** The header content of the modal. */
header?: ReactNode;
/** The main content of the modal. */
children: ReactNode;
/** The footer content of the modal. */
footer?: ReactNode;
/** A callback function to handle the close event. */
onClose?: () => void;
}
/**
* A ref object for the ModusModal component.
*/
export interface ModusModalRef {
/** Opens the modal. */
openModal: () => void;
/** Closes the modal. */
closeModal: () => void;
}
/**
* Renders a Modus modal component.
* @param {ModusModalProps} props - The component props.
* @param {React.Ref<ModusModalRef>} ref - The ref object for the modal.
* @returns {JSX.Element} The rendered modal component.
*/
const ModusModal = forwardRef<ModusModalRef, ModusModalProps>(
(
{
modalId,
ariaLabel,
backdrop = 'default',
position = 'center',
fullscreen = false,
showFullscreenToggle = false,
showClose = true,
customClass,
header,
children,
footer,
onClose,
},
ref
) => {
const modalRef = useRef<HTMLModusWcModalElement>(null);
const openModal = () => {
if (modalRef.current) {
const dialog = modalRef.current.querySelector(
"dialog"
) as HTMLDialogElement;
if (dialog) {
dialog.showModal();
}
}
};
const closeModal = () => {
if (modalRef.current) {
const dialog = modalRef.current.querySelector(
"dialog"
) as HTMLDialogElement;
if (dialog) {
dialog.close();
}
}
};
useImperativeHandle(ref, () => ({
openModal,
closeModal,
}));
// Handle modal events
useEffect(() => {
const modal = modalRef.current;
if (modal) {
const handleClose = () => {
onClose?.();
};
const dialogElement = modal.querySelector("dialog");
if (dialogElement) {
dialogElement.addEventListener("close", handleClose);
return () => {
dialogElement.removeEventListener("close", handleClose);
};
}
}
}, [onClose]);
return (
<ModusWcModal
ref={modalRef}
modal-id={modalId}
aria-label={ariaLabel}
backdrop={backdrop}
position={position}
fullscreen={fullscreen}
show-fullscreen-toggle={showFullscreenToggle}
show-close={showClose}
custom-class={customClass}
>
{header && <div slot="header">{header}</div>}
<div slot="content">{children}</div>
{footer && <div slot="footer">{footer}</div>}
</ModusWcModal>
);
}
);
ModusModal.displayName = "ModusModal";
export default ModusModal;
import { useRef } from "react";
import ModusButton from "./components/ModusButton";
import ModusModal, { type ModusModalRef } from "./components/ModusModal";
function MyComponent() {
const modalRef = useRef<ModusModalRef>(null);
return (
<>
<ModusButton
onButtonClick={() => {
modalRef.current?.openModal();
}}
>
Open Modal
</ModusButton>
<ModusModal
ref={modalRef}
modalId="my-modal"
ariaLabel="Example modal"
onClose={() => {
console.log("Modal closed");
}}
header={
<div className="text-xl font-semibold text-foreground">
Modal Title
</div>
}
footer={
<div className="flex gap-2">
<ModusButton
variant="borderless"
onButtonClick={() => {
modalRef.current?.closeModal();
}}
>
Cancel
</ModusButton>
<ModusButton
onButtonClick={() => {
modalRef.current?.closeModal();
}}
>
Confirm
</ModusButton>
</div>
}
>
<div className="text-sm text-foreground opacity-80">
Modal content goes here.
</div>
</ModusModal>
</>
);
}
const ModusModal = forwardRef<ModusModalRef, ModusModalProps>(
(props, ref) => {
// Component implementation
}
);
useImperativeHandle(ref, () => ({
openModal,
closeModal,
}));
const openModal = () => {
if (modalRef.current) {
const dialog = modalRef.current.querySelector(
"dialog"
) as HTMLDialogElement;
if (dialog) {
dialog.showModal();
}
}
};
Critical: Always use querySelector("dialog") - don't try to access dialog methods directly on the web component.
Modus modals use slots for content:
<ModusWcModal>
{header && <div slot="header">{header}</div>}
<div slot="content">{children}</div>
{footer && <div slot="footer">{footer}</div>}
</ModusWcModal>
Listen to the native dialog close event:
useEffect(() => {
const modal = modalRef.current;
if (modal) {
const dialogElement = modal.querySelector("dialog");
if (dialogElement) {
dialogElement.addEventListener("close", handleClose);
return () => {
dialogElement.removeEventListener("close", handleClose);
};
}
}
}, [onClose]);
<ModusModal
ref={modalRef}
modalId="centered-modal"
position="center"
// ... other props
/>
<ModusModal
ref={modalRef}
modalId="top-modal"
position="top"
// ... other props
/>
<ModusModal
ref={modalRef}
modalId="bottom-modal"
position="bottom"
// ... other props
/>
<ModusModal
ref={modalRef}
modalId="fullscreen-modal"
fullscreen={true}
showFullscreenToggle={true}
// ... other props
/>
<ModusModal
ref={modalRef}
modalId="static-modal"
backdrop="static"
// ... other props
/>
displayName for forwardRef componentsquerySelector("dialog"), not direct property accessmodalRef.current before useclose event on dialog element, not web componentopenModal() is calledcloseModal() is calledonClose callback fires when modal closessrc/components/ModusModal.tsx - Complete implementationsrc/demos/modal-demo/page.tsx - Usage examples.cursor/rules/modus-modal-implementation-react.mdc - Detailed documentation