flowlight
Version:
A lightweight command interface library with floating button, search functionality, and React integration
182 lines (154 loc) • 5.39 kB
JavaScript
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';