React component to wrap content in Collapsible element with trigger to open and close.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Comprehensive callback system for responding to state changes and user interactions throughout the collapsible component lifecycle.
Custom control over trigger click behavior and state management.
/**
* Custom handler for trigger click events
* When provided, overrides default open/close behavior
* Receives accordionPosition if specified
* @param accordionPosition - The accordionPosition prop value if provided
*/
handleTriggerClick?: (accordionPosition?: string | number) => void;Usage Examples:
// Basic controlled usage
const [isOpen, setIsOpen] = useState(false);
<Collapsible
trigger="Custom click handler"
open={isOpen}
handleTriggerClick={() => {
console.log('Trigger clicked!');
setIsOpen(!isOpen);
}}
>
<p>Controlled content</p>
</Collapsible>
// Accordion pattern with position tracking
const [openSections, setOpenSections] = useState(new Set());
const handleAccordionClick = (position) => {
const newOpenSections = new Set(openSections);
if (newOpenSections.has(position)) {
newOpenSections.delete(position);
} else {
newOpenSections.add(position);
}
setOpenSections(newOpenSections);
};
{items.map((item, index) => (
<Collapsible
key={item.id}
trigger={item.title}
open={openSections.has(index)}
accordionPosition={index}
handleTriggerClick={handleAccordionClick}
>
<div>{item.content}</div>
</Collapsible>
))}Events that fire at different stages of the opening and closing animations.
/**
* Called when the opening animation begins
* Fires immediately when user clicks to open or open prop becomes true
*/
onOpening?: () => void;
/**
* Called when the opening animation completes
* Fires after the component is fully expanded and animation finished
*/
onOpen?: () => void;
/**
* Called when the closing animation begins
* Fires immediately when user clicks to close or open prop becomes false
*/
onClosing?: () => void;
/**
* Called when the closing animation completes
* Fires after the component is fully collapsed and animation finished
*/
onClose?: () => void;Animation Timeline:
open prop changesonOpening or onClosing fires immediatelytransitionTime or transitionCloseTime)onOpen or onClose fires when animation completesUsage Examples:
// Track animation state
const [animationState, setAnimationState] = useState('closed');
<Collapsible
trigger="Animation tracking"
onOpening={() => {
console.log('Starting to open...');
setAnimationState('opening');
}}
onOpen={() => {
console.log('Fully opened!');
setAnimationState('open');
}}
onClosing={() => {
console.log('Starting to close...');
setAnimationState('closing');
}}
onClose={() => {
console.log('Fully closed!');
setAnimationState('closed');
}}
>
<div>Animation state: {animationState}</div>
</Collapsible>
// Analytics tracking
<Collapsible
trigger="Tracked content"
onOpen={() => {
analytics.track('collapsible_opened', {
section: 'faq-section-1'
});
}}
onClose={() => {
analytics.track('collapsible_closed', {
section: 'faq-section-1'
});
}}
>
<p>Content with analytics tracking</p>
</Collapsible>Events that fire specifically in response to user clicking the trigger element.
/**
* Called when user clicks trigger to open the collapsible
* Only fires for actual user clicks, not programmatic state changes
*/
onTriggerOpening?: () => void;
/**
* Called when user clicks trigger to close the collapsible
* Only fires for actual user clicks, not programmatic state changes
*/
onTriggerClosing?: () => void;Difference from Animation Events:
onOpening, onClosing) fire for both user clicks AND programmatic open prop changesonTriggerOpening, onTriggerClosing) fire ONLY when user actually clicks the triggerUsage Examples:
// Distinguish between user actions and programmatic changes
const [userInteractions, setUserInteractions] = useState(0);
<Collapsible
trigger="User interaction tracking"
onOpening={() => console.log('Opening (any cause)')}
onTriggerOpening={() => {
console.log('User clicked to open');
setUserInteractions(prev => prev + 1);
}}
onClosing={() => console.log('Closing (any cause)')}
onTriggerClosing={() => {
console.log('User clicked to close');
setUserInteractions(prev => prev + 1);
}}
>
<p>User interactions: {userInteractions}</p>
</Collapsible>
// Auto-close with user action detection
const [autoCloseTimer, setAutoCloseTimer] = useState(null);
<Collapsible
trigger="Auto-closing content"
onTriggerOpening={() => {
// Clear any existing auto-close timer when user opens
if (autoCloseTimer) {
clearTimeout(autoCloseTimer);
setAutoCloseTimer(null);
}
}}
onOpen={() => {
// Set auto-close timer after opening completes
const timer = setTimeout(() => {
// Programmatically close (won't trigger onTriggerClosing)
setIsOpen(false);
}, 5000);
setAutoCloseTimer(timer);
}}
>
<p>This will auto-close in 5 seconds unless you interact with it</p>
</Collapsible>const EventLogger = () => {
const [logs, setLogs] = useState([]);
const [isOpen, setIsOpen] = useState(false);
const addLog = (message) => {
setLogs(prev => [...prev, `${new Date().toLocaleTimeString()}: ${message}`]);
};
return (
<div>
<Collapsible
trigger="Complete event logging"
open={isOpen}
handleTriggerClick={() => {
addLog('handleTriggerClick called');
setIsOpen(!isOpen);
}}
onTriggerOpening={() => addLog('onTriggerOpening: User clicked to open')}
onOpening={() => addLog('onOpening: Opening animation started')}
onOpen={() => addLog('onOpen: Opening animation completed')}
onTriggerClosing={() => addLog('onTriggerClosing: User clicked to close')}
onClosing={() => addLog('onClosing: Closing animation started')}
onClose={() => addLog('onClose: Closing animation completed')}
transitionTime={1000} // Longer animation to see the sequence
>
<div>
<h4>Event Log:</h4>
<ul>
{logs.map((log, index) => (
<li key={index}>{log}</li>
))}
</ul>
</div>
</Collapsible>
<button onClick={() => setIsOpen(!isOpen)}>
Programmatic Toggle (won't trigger onTrigger* events)
</button>
</div>
);
};const AccordionWithEvents = () => {
const [openItems, setOpenItems] = useState(new Set());
const [lastAction, setLastAction] = useState('');
const handleItemClick = (position) => {
const newOpenItems = new Set(openItems);
const wasOpen = newOpenItems.has(position);
if (wasOpen) {
newOpenItems.delete(position);
setLastAction(`Closed item ${position}`);
} else {
newOpenItems.add(position);
setLastAction(`Opened item ${position}`);
}
setOpenItems(newOpenItems);
};
const items = [
{ title: 'Section 1', content: 'Content for section 1' },
{ title: 'Section 2', content: 'Content for section 2' },
{ title: 'Section 3', content: 'Content for section 3' }
];
return (
<div>
<p>Last action: {lastAction}</p>
{items.map((item, index) => (
<Collapsible
key={index}
trigger={item.title}
open={openItems.has(index)}
accordionPosition={index}
handleTriggerClick={handleItemClick}
onOpen={() => console.log(`Section ${index} finished opening`)}
onClose={() => console.log(`Section ${index} finished closing`)}
>
<div>{item.content}</div>
</Collapsible>
))}
</div>
);
};Install with Tessl CLI
npx tessl i tessl/npm-react-collapsible