@lerx/promise-modal
v0.13.1
Published
Universal React modal utility that can be used outside React components with promise-based results for alert, confirm, and prompt modals
Maintainers
Readme
@lerx/promise-modal
Overview
@lerx/promise-modal is a universal modal utility based on React.
Key features include:
- Can be used even in places not included in React components
- After opening a modal, you can retrieve the result as a promise
- Supports various modal types (alert, confirm, prompt)
- Highly customizable component structure
Installation
yarn add @lerx/promise-modalor
npm install @lerx/promise-modalCompatibility
Supported environments:
- Node.js 16.11.0 or later
- Modern browsers (Chrome 94+, Firefox 93+, Safari 15+)
For legacy environment support: Please use a transpiler like Babel to transform the code for your target environment.
How to Use
1. Setting up the Modal Provider
Install ModalProvider at the root of your application:
import { ModalProvider } from '@lerx/promise-modal';
function App() {
return (
<ModalProvider>
<YourApp />
</ModalProvider>
);
}You can also apply custom options and components:
import { ModalProvider } from '@lerx/promise-modal';
import {
CustomBackground,
CustomContent,
CustomFooter,
CustomForeground,
CustomSubtitle,
CustomTitle,
} from './components';
function App() {
return (
<ModalProvider
ForegroundComponent={CustomForeground}
BackgroundComponent={CustomBackground}
TitleComponent={CustomTitle}
SubtitleComponent={CustomSubtitle}
ContentComponent={CustomContent}
FooterComponent={CustomFooter}
options={{
duration: '250ms', // Animation duration
backdrop: 'rgba(0, 0, 0, 0.35)', // Background overlay color
manualDestroy: false, // Default auto-destroy behavior
closeOnBackdropClick: true, // Default backdrop click behavior
}}
context={{
// Context values accessible in all modal components
theme: 'light',
locale: 'en-US',
}}
>
<YourApp />
</ModalProvider>
);
}2. Using Basic Modals
Alert Modal
Alert modals provide simple information display with a confirmation button.
import { alert } from '@lerx/promise-modal';
// Basic usage
async function showAlert() {
await alert({
title: 'Notification',
content: 'The task has been completed.',
});
console.log('User closed the modal.');
}
// Using various options
async function showDetailedAlert() {
await alert({
subtype: 'success', // 'info' | 'success' | 'warning' | 'error'
title: 'Success',
subtitle: 'Details',
content: 'The task has been successfully completed.',
dimmed: true, // Dim the background
closeOnBackdropClick: true, // Close on backdrop click
// If use close animation, you need to set manualDestroy to true
manualDestroy: false, // Auto-destroy (false: auto, true: manual)
// Data to pass to the background
background: {
data: 'custom-data',
},
});
}Confirm Modal
Confirm modals are used for actions that require user confirmation.
import { confirm } from '@lerx/promise-modal';
async function showConfirm() {
const result = await confirm({
title: 'Confirm',
content: 'Are you sure you want to delete this?',
// Custom footer text
footer: {
confirm: 'Delete',
cancel: 'Cancel',
},
});
if (result) {
console.log('User clicked confirm.');
// Execute delete logic
} else {
console.log('User clicked cancel.');
}
}Prompt Modal
Prompt modals are used to receive input from users.
import { prompt } from '@lerx/promise-modal';
async function showPrompt() {
// Text input
const name = await prompt<string>({
title: 'Enter Name',
content: 'Please enter your name.',
defaultValue: '', // Default value
Input: ({ value, onChange }) => {
// Important: value is the current value, onChange is the function to update the value
return (
<input
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder="Enter name"
/>
);
},
// Validate input to control the confirmation button
disabled: (value) => value.length < 2,
});
console.log('Entered name:', name);
// Complex data input
const userInfo = await prompt<{ name: string; age: number }>({
title: 'User Information',
defaultValue: { name: '', age: 0 },
Input: ({ value, onChange }) => (
<div>
<input
value={value.name}
onChange={(e) => onChange({ ...value, name: e.target.value })}
placeholder="Name"
/>
<input
type="number"
value={value.age}
onChange={(e) => onChange({ ...value, age: Number(e.target.value) })}
placeholder="Age"
/>
</div>
),
});
console.log('User info:', userInfo);
}3. Using Custom Components
You can customize the appearance and behavior of modals:
import { css } from '@emotion/css';
import { ModalProvider, alert, confirm } from '@lerx/promise-modal';
// Custom foreground component
const CustomForegroundComponent = ({ children, type, ...props }) => {
return (
<div
className={css`
background-color: #ffffff;
border-radius: 12px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1);
width: 90%;
max-width: 500px;
padding: 24px;
position: relative;
`}
>
{children}
</div>
);
};
// Custom background component (utilizing background data)
const CustomBackgroundComponent = ({ children, onClick, background }) => {
// Apply different styles based on background data
const getBgColor = () => {
if (background?.data === 'alert') return 'rgba(0, 0, 0, 0.7)';
if (background?.data === 'confirm') return 'rgba(0, 0, 0, 0.5)';
if (background?.data === 'prompt') return 'rgba(0, 0, 0, 0.6)';
return 'rgba(0, 0, 0, 0.4)';
};
return (
<div
className={css`
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
background-color: ${getBgColor()};
z-index: 1000;
`}
onClick={onClick}
>
{children}
</div>
);
};
// Custom title component
const CustomTitleComponent = ({ children, context }) => {
return (
<h2
className={css`
font-size: 24px;
margin-bottom: 8px;
color: ${context?.theme === 'dark' ? '#ffffff' : '#333333'};
`}
>
{children}
</h2>
);
};
// Custom subtitle component
const CustomSubtitleComponent = ({ children, context }) => {
return (
<h3
className={css`
font-size: 16px;
margin-bottom: 16px;
color: ${context?.theme === 'dark' ? '#cccccc' : '#666666'};
`}
>
{children}
</h3>
);
};
// Custom content component
const CustomContentComponent = ({ children, context }) => {
return (
<div
className={css`
margin-bottom: 24px;
color: ${context?.theme === 'dark' ? '#eeeeee' : '#444444'};
`}
>
{children}
</div>
);
};
// Custom footer component
const CustomFooterComponent = ({
onConfirm,
onClose,
onCancel,
type,
disabled,
}) => {
return (
<div
className={css`
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 24px;
`}
>
{/* Display cancel button for confirm and prompt modals */}
{(type === 'confirm' || type === 'prompt') && (
<button
className={css`
padding: 8px 16px;
border: 1px solid #ccc;
background: none;
border-radius: 4px;
cursor: pointer;
&:hover {
background-color: #f3f3f3;
}
`}
onClick={type === 'confirm' ? () => onConfirm(false) : onCancel}
>
Cancel
</button>
)}
<button
className={css`
padding: 8px 16px;
background-color: #4a90e2;
color: white;
border: none;
border-radius: 4px;
cursor: ${disabled ? 'not-allowed' : 'pointer'};
opacity: ${disabled ? 0.6 : 1};
&:hover {
background-color: ${disabled ? '#4a90e2' : '#357ac7'};
}
`}
onClick={() => onConfirm(type === 'confirm' ? true : undefined)}
disabled={disabled}
>
Confirm
</button>
</div>
);
};
// Global setup in provider
function App() {
return (
<ModalProvider
ForegroundComponent={CustomForegroundComponent}
BackgroundComponent={CustomBackgroundComponent}
TitleComponent={CustomTitleComponent}
SubtitleComponent={CustomSubtitleComponent}
ContentComponent={CustomContentComponent}
FooterComponent={CustomFooterComponent}
options={{
duration: 300, // Animation duration (ms)
}}
context={{
// Context accessible in all modals
theme: 'light',
locale: 'en-US',
}}
>
<YourApp />
</ModalProvider>
);
}
// Applying custom components to specific modals
async function showCustomAlert() {
await alert({
title: 'Notification',
content: 'Content',
// Custom component only for this modal
ForegroundComponent: ({ children }) => (
<div
className={css`
background-color: #f0f8ff;
padding: 30px;
border-radius: 16px;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
`}
>
{children}
</div>
),
// Pass background data
background: {
data: 'custom-alert',
},
});
}4. Using Custom Anchors and Initialization
You can specify the DOM element where modals will be rendered:
import { useEffect, useRef } from 'react';
import {
ModalProvider,
ModalProviderHandle,
alert,
useInitializeModal,
} from '@lerx/promise-modal';
// Using refs
function CustomAnchorExample() {
// Modal provider handle reference
const modalProviderRef = useRef<ModalProviderHandle>(null);
// Container reference for modal display
const modalContainerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
// Initialize modal when container is ready
if (modalContainerRef.current && modalProviderRef.current) {
modalProviderRef.current.initialize(modalContainerRef.current);
}
}, []);
return (
<ModalProvider ref={modalProviderRef}>
<div>
{/* Modals will render inside this div */}
<div
ref={modalContainerRef}
style={{
backgroundColor: '#f0f0f0',
width: '100%',
height: '500px',
position: 'relative',
overflow: 'hidden',
}}
/>
<button
onClick={() =>
alert({ title: 'Notice', content: 'Displayed in custom anchor.' })
}
>
Show Modal
</button>
</div>
</ModalProvider>
);
}
// Using the useInitializeModal hook
function CustomAnchorWithHookExample() {
// useInitializeModal hook (manual mode: manual initialization)
const { initialize, portal } = useInitializeModal({ mode: 'manual' });
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (containerRef.current) {
// Initialize modal with container element
initialize(containerRef.current);
}
}, [initialize]);
return (
<div>
{/* Container for modal rendering */}
<div
ref={containerRef}
style={{
backgroundColor: '#f0f0f0',
width: '100%',
height: '500px',
}}
/>
<button
onClick={() =>
alert({ title: 'Notice', content: 'Displayed in custom anchor.' })
}
>
Show Modal
</button>
{/* Portal rendering in another location (optional) */}
<div id="another-container">{portal}</div>
</div>
);
}5. Implementing Toast Messages
You can implement toast message functionality using promise-modal. This example is based on actual implementation in the project:
import React, { type ReactNode, useEffect, useRef } from 'react';
import { css } from '@emotion/css';
import {
ModalFrameProps,
alert,
useDestroyAfter,
useModalAnimation,
useModalDuration,
} from '@lerx/promise-modal';
// Toast foreground component definition
const ToastForeground = ({
id,
visible,
children,
onClose,
hideAfterMs = 3000,
}) => {
const modalRef = useRef(null);
const { duration } = useModalDuration();
// Auto-close after specified time
useEffect(() => {
const timer = setTimeout(onClose, hideAfterMs);
return () => clearTimeout(timer);
}, [onClose, hideAfterMs]);
// Animation handling
useModalAnimation(visible, {
onVisible: () => {
modalRef.current?.classList.add('visible');
},
onHidden: () => {
modalRef.current?.classList.remove('visible');
},
});
// Destroy after closing
useDestroyAfter(id, duration);
return (
<div
ref={modalRef}
className={css`
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%) translateY(100px);
opacity: 0;
transition:
transform ${duration}ms,
opacity ${duration}ms;
&.visible {
transform: translateX(-50%) translateY(0);
opacity: 1;
}
`}
>
{children}
</div>
);
};
// Toast message interface
interface ToastProps {
message: ReactNode;
duration?: number;
}
// Handler to remove previous toast
let onDestroyPrevToast: () => void;
// Toast display function
export const toast = ({ message, duration = 1250 }: ToastProps) => {
// Remove previous toast if exists
onDestroyPrevToast?.();
return alert({
content: message,
ForegroundComponent: (props: ModalFrameProps) => {
// Store destroy function of new toast
onDestroyPrevToast = props.onDestroy;
return <ToastForeground {...props} hideAfterMs={duration} />;
},
footer: false, // Hide footer
dimmed: false, // Disable background dim
closeOnBackdropClick: false, // Disable closing on backdrop click
});
};
// Usage example
function showToastExample() {
toast({
message: (
<div
className={css`
background-color: #333;
color: white;
padding: 12px 24px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
`}
>
Task completed!
</div>
),
duration: 2000, // Auto-close after 2 seconds
});
}6. Various Modal Configuration Options
Examples utilizing various modal configuration options:
import { alert, confirm } from '@lerx/promise-modal';
// Basic alert modal
async function showBasicAlert() {
await alert({
title: 'Basic Notification',
content: 'This is an alert modal with default settings.',
});
}
// Modal settings by type
async function showModalByType() {
// Success notification
await alert({
title: 'Success',
content: 'Task completed successfully.',
subtype: 'success',
dimmed: true,
});
// Warning confirmation
const result = await confirm({
title: 'Warning',
content: 'This action cannot be undone. Continue?',
subtype: 'warning',
closeOnBackdropClick: false, // Prevent closing by backdrop click
});
// Error notification
await alert({
title: 'Error',
content: 'Unable to complete the task.',
subtype: 'error',
// Custom footer text
footer: {
confirm: 'I understand',
},
});
}
// Using manual destroy mode
async function showManualDestroyModal() {
await alert({
title: 'Notification',
content:
"This modal won't close on backdrop click and requires confirmation button to close.",
manualDestroy: true, // Enable manual destroy mode
closeOnBackdropClick: false, // Prevent closing by backdrop click
});
}
// Using background data
async function showModalWithBackground() {
await alert({
title: 'Background Data Usage',
content: 'This modal passes data to the background component.',
background: {
data: 'custom-background-data',
opacity: 0.8,
blur: '5px',
},
});
}API Reference
Core Functions
alert(options)
Opens a simple alert modal to display information to the user.
Parameters:
options: Alert modal configuration objecttitle?: Modal title (ReactNode)subtitle?: Subtitle (ReactNode)content?: Modal content (ReactNode or component)subtype?: Modal type ('info' | 'success' | 'warning' | 'error')background?: Background settings (ModalBackground object)footer?: Footer settings- Function:
(props: FooterComponentProps) => ReactNode - Object:
{ confirm?: string; hideConfirm?: boolean } false: Hide footer
- Function:
dimmed?: Whether to dim the background (boolean)manualDestroy?: Enable manual destroy mode (boolean)closeOnBackdropClick?: Whether to close on backdrop click (boolean)ForegroundComponent?: Custom foreground componentBackgroundComponent?: Custom background component
Returns: Promise<void> - Resolved when the modal is closed
// Example
await alert({
title: 'Notification',
content: 'Content',
subtype: 'info',
closeOnBackdropClick: true,
});confirm(options)
Opens a confirmation modal that requests user confirmation.
Parameters:
options: Confirm modal configuration- Same options as
alert, plus: footer?: Footer settings- Function:
(props: FooterComponentProps) => ReactNode - Object:
{ confirm?: string; cancel?: string; hideConfirm?: boolean; hideCancel?: boolean } false: Hide footer
- Function:
- Same options as
Returns: Promise<boolean> - Resolves to true if confirmed, false if canceled
// Example
const result = await confirm({
title: 'Confirm',
content: 'Do you want to proceed?',
footer: {
confirm: 'Confirm',
cancel: 'Cancel',
},
});prompt<T>(options)
Opens a prompt modal to receive input from the user.
Parameters:
options: Prompt modal configuration object- Same options as
alert, plus: Input: Function to render input field(props: PromptInputProps<T>) => ReactNode- props:
{ value: T; onChange: (value: T) => void }
defaultValue?: Default value (T)disabled?: Function to determine if confirm button should be disabled(value: T | undefined) => boolean
returnOnCancel?: If true, cancel resolves with the current input value instead of null (boolean)footer?: Footer settings (similar to confirm)
- Same options as
Returns: Promise<T | null> - Resolves to the input value on confirm, or null on cancel
Upgrade note:
prompt()now types cancellation explicitly — the return isPromise<T | null>and thedisabledcallback receivesT | undefined. Consumers that previously annotated these as non-null may hit new TypeScript errors on upgrade; the runtime behaviour (cancel resolvesnull) is unchanged.
// Example
const value = await prompt<string>({
title: 'Input',
defaultValue: '',
Input: ({ value, onChange }) => (
<input
value={value}
onChange={(e) => onChange(e.target.value)}
/>
),
disabled: (value) => value.trim() === '',
});Components
ModalProvider (or BootstrapProvider)
Component that initializes and provides the modal service.
Props:
ForegroundComponent?: Custom foreground component(props: WrapperComponentProps) => ReactNode
BackgroundComponent?: Custom background component(props: WrapperComponentProps) => ReactNode
TitleComponent?: Custom title componentSubtitleComponent?: Custom subtitle componentContentComponent?: Custom content componentFooterComponent?: Custom footer component(props: FooterComponentProps) => ReactNode
options?: Global modal optionsduration?: Animation duration (milliseconds)backdrop?: Backdrop click handling- Other options...
context?: Context object to pass to modal componentsusePathname?: Custom pathname hook function
Handle:
initialize: Method to manually initialize modal service
// Example using ref for control
import { useRef } from 'react';
import { ModalProvider, ModalProviderHandle } from '@lerx/promise-modal';
function App() {
const modalProviderRef = useRef<ModalProviderHandle>(null);
const handleInitialize = () => {
modalProviderRef.current?.initialize();
};
return (
<ModalProvider ref={modalProviderRef}>
<YourApp />
</ModalProvider>
);
}Hooks
useModal
Returns modal handler functions that share the component's lifecycle.
Key Features:
Unlike static handlers (alert, confirm, prompt), modals created with useModal automatically clean up when the component unmounts. This is useful when you want to ensure that modals opened within a specific component are automatically closed when that component is removed.
Returns:
alert: Alert modal handler functionconfirm: Confirm modal handler functionprompt: Prompt modal handler function
import { useModal } from '@lerx/promise-modal';
function MyComponent() {
const modal = useModal();
const handleShowAlert = async () => {
// Modal will be automatically cleaned up when MyComponent unmounts
await modal.alert({
title: 'Notice',
content: 'This modal is tied to the component lifecycle.',
});
};
const handleShowConfirm = async () => {
const result = await modal.confirm({
title: 'Confirm',
content: 'Do you want to proceed?',
});
console.log('Result:', result);
};
return (
<div>
<button onClick={handleShowAlert}>Show Alert</button>
<button onClick={handleShowConfirm}>Show Confirm</button>
</div>
);
}Comparison with Static Handlers:
| Feature | Static Handlers | useModal Hook | | -------------- | ----------------------------- | ---------------------------- | | Lifecycle | Independent of components | Tied to component lifecycle | | Cleanup | Manual cleanup required | Automatic cleanup on unmount | | Usage Location | Anywhere (even outside React) | Inside React components only |
useModalOptions
Read global options for modals.
import { useModalOptions } from '@lerx/promise-modal';
function Component() {
// ConfigurationContextProps
const options = useModalOptions();
// ...
}useModalDuration
Read modal animation duration.
import { useModalDuration } from '@lerx/promise-modal';
function Component() {
// duration is 300ms, milliseconds is 300
const { duration, milliseconds } = useModalDuration();
// ...
}useModalBackdrop
Read modal backdrop settings.
import { useModalBackdrop } from '@lerx/promise-modal';
function Component() {
// backdrop is Color(#000000~#ffffff or rgba(0,0,0,0.5))
const backdrop = useModalBackdrop();
// ...
}useInitializeModal
Initializes the modal service. Usually called automatically by ModalProvider.
import { useInitializeModal } from '@lerx/promise-modal';
function Component() {
const { initialize } = useInitializeModal();
// Manually initialize if needed
useEffect(() => {
initialize();
}, [initialize]);
// ...
}useSubscribeModal
Subscribes to modal state changes.
import { useSubscribeModal } from '@lerx/promise-modal';
function Component({ modal }) {
// Component rerenders when modal state changes
const version = useSubscribeModal(modal);
// ...
}useDestroyAfter
Automatically destroys a modal after specified time.
import { useDestroyAfter } from '@lerx/promise-modal';
function Component({ modalId }) {
// Auto-close modal after 3 seconds
useDestroyAfter(modalId, 3000);
// ...
}useActiveModalCount
Returns the number of currently active modals.
import { useActiveModalCount } from '@lerx/promise-modal';
function Component() {
const count = useActiveModalCount();
return (
<div>
Currently open modals: {count}
</div>
);
}useModalAnimation
Provides modal animation state and control.
import { useModalAnimation } from '@lerx/promise-modal';
export const Foreground = ({
id,
visible,
children,
onClose,
hideAfterMs,
}: PropsWithChildren<ForegroundProps>) => {
const modalRef = useRef<HTMLDivElement>(null);
useModalAnimation(visible, {
onVisible: () => {
modalRef.current?.classList.add(styles.visible);
},
onHidden: () => {
modalRef.current?.classList.remove(styles.visible);
},
});
...
}Type Definitions
The library provides various types for TypeScript compatibility:
ModalFrameProps
Properties passed to the modal frame component.
interface ModalFrameProps<Context = any, B = any> {
id: number;
type: 'alert' | 'confirm' | 'prompt';
alive: boolean;
visible: boolean;
initiator: string;
manualDestroy: boolean;
closeOnBackdropClick: boolean;
background?: ModalBackground<B>;
onConfirm: () => void;
onClose: () => void;
onChange: (value: any) => void;
onDestroy: () => void;
onChangeOrder: Function;
context: Context;
}FooterComponentProps
Properties passed to the footer component.
interface FooterComponentProps {
onConfirm: () => void;
onClose: () => void;
// Other props...
}PromptInputProps<T>
Properties passed to the input component in prompt modals.
interface PromptInputProps<T> {
value: T;
onChange: (value: T) => void;
}Additional types provided:
ModalBackground: Modal background settings typeAlertContentProps: Alert modal content component propsConfirmContentProps: Confirm modal content component propsPromptContentProps: Prompt modal content component propsWrapperComponentProps: Modal wrapper component props
Advanced Usage Examples
1. Nested Modals (Opening Modals inside Other Modals)
You can open modals inside other modals to create complex user interactions.
import { alert, confirm, prompt } from '@lerx/promise-modal';
// Multi-step modal workflow example
async function multiStepProcess() {
// First modal: confirm to proceed
const shouldProceed = await confirm({
title: 'Start Task',
content: 'This task involves multiple steps. Do you want to continue?',
footer: {
confirm: 'Proceed',
cancel: 'Cancel',
},
});
if (!shouldProceed) return;
// Second modal: get user input
const userName = await prompt<string>({
title: 'User Information',
content: 'Please enter your name.',
defaultValue: '',
Input: ({ value, onChange }) => (
<input
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder="Name"
/>
),
});
if (!userName) return;
// Third modal: final confirmation
const confirmed = await confirm({
title: 'Final Confirmation',
content: `${userName}, are you sure you want to proceed?`,
subtype: 'warning',
});
if (confirmed) {
// Last modal: completion notification
await alert({
title: 'Complete',
content: `${userName}, the task has been completed successfully.`,
subtype: 'success',
});
}
}
// Opening modal from within a footer
async function nestedModalInFooter() {
const result = await confirm({
title: 'Confirmation Required',
content: 'Do you want to proceed with this task?',
// Custom footer that opens another modal
footer: ({ onConfirm, onClose }) => {
const handleConfirm = async () => {
// Open another modal when confirm button is clicked
const isConfirmed = await confirm({
title: 'Final Confirmation',
content: 'Are you really sure? This action cannot be undone.',
closeOnBackdropClick: false,
// Apply different design to second modal
ForegroundComponent: CustomForegroundComponent,
});
// Process first modal result based on second modal result
if (isConfirmed) onConfirm();
else onClose();
};
return (
<div>
<button onClick={onClose}>Cancel</button>
<button onClick={handleConfirm}>Next Step</button>
</div>
);
},
});
if (result) {
console.log('Proceeding with the task.');
}
}
// Opening modal from within a prompt modal
async function promptWithNestedModal() {
const value = await prompt<{ title: string; description: string }>({
title: 'Create Content',
defaultValue: { title: '', description: '' },
Input: ({ value, onChange }) => {
// Show help modal when help button is clicked
const showHelp = () => {
alert({
title: 'Help',
content: 'Enter a title and description. Title is required.',
});
};
return (
<div>
<div>
<label>Title</label>
<input
value={value.title}
onChange={(e) => onChange({ ...value, title: e.target.value })}
/>
</div>
<div>
<label>Description</label>
<textarea
value={value.description}
onChange={(e) =>
onChange({ ...value, description: e.target.value })
}
/>
</div>
<button type="button" onClick={showHelp}>
Help
</button>
</div>
);
},
disabled: (value) => !value.title,
});
console.log('Input value:', value);
}License
This repository is licensed under the MIT License. Please refer to the LICENSE file for details.
Contact
For inquiries or suggestions related to the project, please create an issue.
