UNPKG

flowlight

Version:

A lightweight command interface library with floating button, search functionality, and React integration

182 lines (154 loc) 5.39 kB
import React, { useEffect, useRef, useState } from 'react'; // Import the modular FlowLight import FlowLight from 'flowlight'; // Global FlowLight instance let globalFlowLightInstance = null; /** * FlowLightProvider - Self-contained component that automatically initializes FlowLight * * @param {Object} props - Component props * @param {Object} props.options - FlowLight configuration options * @param {React.ReactNode} props.children - Child components (optional) */ export function FlowLightProvider({ options = {}, children }) { const [isReady, setIsReady] = useState(false); const [isSearchVisible, setIsSearchVisible] = useState(false); const [error, setError] = useState(null); const flowlightRef = useRef(null); // Initialize FlowLight useEffect(() => { // If already initialized globally, use that instance if (globalFlowLightInstance) { flowlightRef.current = globalFlowLightInstance; setIsReady(true); setIsSearchVisible(globalFlowLightInstance.getSearchState().isVisible); return; } try { // Create FlowLight instance flowlightRef.current = new FlowLight({ ...options, debug: options.debug || false }); // Store globally globalFlowLightInstance = flowlightRef.current; // Listen to search state changes flowlightRef.current.eventBus.on('search:shown', () => { setIsSearchVisible(true); }); flowlightRef.current.eventBus.on('search:hidden', () => { setIsSearchVisible(false); }); // Mark as ready setIsReady(true); } catch (err) { console.error('Failed to initialize FlowLight:', err); setError(err); } // Cleanup on unmount (only if this is the last provider) return () => { // Don't destroy if other providers might be using it // The global instance will be cleaned up when the page unloads }; }, []); // Empty dependency array - only run once // Update options when they change useEffect(() => { if (flowlightRef.current && isReady) { flowlightRef.current.updateOptions(options); } }, [options, isReady]); // Return children if provided, otherwise return null return children || null; } /** * useFlowLight - React hook to access FlowLight functionality * * @param {Object} options - FlowLight configuration options (optional if already initialized) * @returns {Object} FlowLight context and methods */ export function useFlowLight(options = {}) { const [isReady, setIsReady] = useState(false); const [isSearchVisible, setIsSearchVisible] = useState(false); const [error, setError] = useState(null); const flowlightRef = useRef(null); // Initialize FlowLight if not already done useEffect(() => { // If already initialized globally, use that instance if (globalFlowLightInstance) { flowlightRef.current = globalFlowLightInstance; setIsReady(true); setIsSearchVisible(globalFlowLightInstance.getSearchState().isVisible); return; } try { // Create FlowLight instance flowlightRef.current = new FlowLight({ ...options, debug: options.debug || false }); // Store globally globalFlowLightInstance = flowlightRef.current; // Listen to search state changes flowlightRef.current.eventBus.on('search:shown', () => { setIsSearchVisible(true); }); flowlightRef.current.eventBus.on('search:hidden', () => { setIsSearchVisible(false); }); // Mark as ready setIsReady(true); } catch (err) { console.error('Failed to initialize FlowLight:', err); setError(err); } }, []); // Empty dependency array - only run once // Update options when they change useEffect(() => { if (flowlightRef.current && isReady) { flowlightRef.current.updateOptions(options); } }, [options, isReady]); const contextValue = { flowlight: flowlightRef.current, isReady, isSearchVisible, error, // Methods showSearch: () => flowlightRef.current?.showSearch(), hideSearch: () => flowlightRef.current?.hideSearch(), toggleSearch: () => flowlightRef.current?.toggleSearch(), updateOptions: (newOptions) => flowlightRef.current?.updateOptions(newOptions), getSearchState: () => flowlightRef.current?.getSearchState(), destroy: () => { if (flowlightRef.current) { flowlightRef.current.destroy(); flowlightRef.current = null; globalFlowLightInstance = null; setIsReady(false); } } }; return contextValue; } /** * useFlowvana - Legacy hook name for backward compatibility * @deprecated Use useFlowLight instead */ export function useFlowvana(options = {}) { console.warn('useFlowvana is deprecated. Use useFlowLight instead.'); return useFlowLight(options); } /** * withFlowLight - Higher-order component for class components * * @param {React.Component} Component - Component to wrap * @returns {React.Component} Wrapped component with FlowLight props */ export function withFlowLight(Component) { return function WrappedComponent(props) { const flowlightProps = useFlowLight(); return React.createElement(Component, { ...props, ...flowlightProps }); }; } // Export FlowLight for direct usage export { default as FlowLight } from 'flowlight';