UNPKG

rn-dynamic-ui-render

Version:
201 lines (178 loc) 5.18 kB
import React, { createContext, useContext } from "react"; import { useTranslation } from "react-i18next"; import PropTypes from "prop-types"; import { ErrorBoundary } from "./utils"; import { handleColor, handleDynamicData, handleStyles, handleVisible, handleInteractions, evaluateCondition, } from "./utils/handlers"; // Create a Context for components and theme const UIContext = createContext({ components: {}, theme: {}, }); /** * Renders a React component based on the provided configuration object. */ const renderComponent = ( e, index, handlers, t, translate, components, theme ) => { if (!e || !e.name) return null; const Component = components[e.name]; if (!Component) { if (__DEV__) console.warn(`Component ${e.name} not found`); return null; } const props = e.props || {}; const componentProps = { ...props }; // Apply handlers handleStyles(props, componentProps, theme, handlers); handleDynamicData(props, componentProps, handlers); handleColor(props, theme, componentProps); handleInteractions(props, handlers, componentProps); // Special case for FlatList if (e.name === "FlatList") { componentProps.keyExtractor = (item, idx) => item?.id?.toString() || idx.toString(); componentProps.ListFooterComponent = () => ( <RNDynamicUIRender data={e.listFooterComponent} handlers={handlers} translate={translate} theme={theme} components={components} /> ); componentProps.ListEmptyComponent = () => ( <RNDynamicUIRender data={e.listEmptyComponent} handlers={handlers} translate={translate} theme={theme} components={components} /> ); componentProps.ListHeaderComponent = () => ( <RNDynamicUIRender data={e.listHeaderComponent} handlers={handlers} translate={translate} theme={theme} components={components} /> ); componentProps.renderItem = ({ item, index: itemIndex }) => ( <RNDynamicUIRender data={e.content} handlers={{ ...handlers, item, index: itemIndex }} translate={translate} theme={theme} components={components} /> ); } // Determine content const content = Array.isArray(e?.content) ? ( <RNDynamicUIRender data={e.content} handlers={handlers} translate={translate} theme={theme} components={components} /> ) : t != null ? ( t(componentProps.textContent || "") || null ) : ( componentProps.textContent || null ); // Visibility check const isVisible = handleVisible(props, handlers) && evaluateCondition(props?.if || true, handlers, handlers?.item); if (!isVisible) return null; return ( <Component {...componentProps} key={index}> {content} </Component> ); }; /** * ComponentRenderer is memoized for performance. */ const ComponentRenderer = React.memo( ({ data, handlers, t, translate, components, theme }) => { if (!Array.isArray(data)) return null; return data.map((e, i) => renderComponent(e, i, handlers, t, translate, components, theme) ); } ); /** * Renders a UI from a JSON schema. * * @param {{ data: array, handlers: object, theme: object, components: object, translate: boolean }} props * @property {array} data - The JSON schema defining the UI * @property {object} handlers - Data and logic context * @property {object} theme - Design tokens for styling * @property {object} components - Custom component registry * @property {boolean} translate - Enable translations via react-i18next * @returns {React.ReactElement} */ const RNDynamicUIRender = ({ data, handlers, theme, components, translate = false, }) => { const { t: translateFunction } = useTranslation(); const t = translate == true ? translateFunction : (str) => str; const contextValue = useContext(UIContext); const effectiveComponents = components ?? contextValue.components; const effectiveTheme = theme ?? contextValue.theme; if (!effectiveComponents || !effectiveTheme) { throw new Error( "RNDynamicUIRender must be provided with components and theme" ); } const content = ( <ErrorBoundary> <ComponentRenderer data={data} handlers={handlers} t={t} translate={translate} components={effectiveComponents} theme={effectiveTheme} /> </ErrorBoundary> ); // Only wrap in provider if new values are passed return components || theme ? ( <UIContext.Provider value={{ components: effectiveComponents, theme: effectiveTheme }} > {content} </UIContext.Provider> ) : ( content ); }; RNDynamicUIRender.propTypes = { data: PropTypes.array, handlers: PropTypes.object, theme: PropTypes.object, components: PropTypes.object, translate: PropTypes.bool, }; export default RNDynamicUIRender;