UNPKG

@selfcommunity/react-ui

Version:

React UI Components to integrate a Community created with SelfCommunity Platform.

604 lines (598 loc) • 29.2 kB
import { __rest } from "tslib"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { forwardRef, useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'; import { SCContributionType, SCFeatureName, SCFeedTypologyType } from '@selfcommunity/types'; import { Endpoints, formatHttpErrorCode, http } from '@selfcommunity/api-services'; import { SCPreferences, UserUtils, useSCPreferences, useSCUser } from '@selfcommunity/react-core'; import { FormattedMessage } from 'react-intl'; import Icon from '@mui/material/Icon'; import { Alert, AlertTitle, Box, Dialog, DialogActions, DialogContent, DialogTitle, Fade, IconButton, Slide, useMediaQuery } from '@mui/material'; import { styled, useTheme } from '@mui/material/styles'; import { COMPOSER_POLL_MIN_CHOICES, COMPOSER_TITLE_MAX_LENGTH, COMPOSER_TYPE_POLL } from '../../constants/Composer'; import { MEDIA_TYPE_SHARE } from '../../constants/Media'; import LoadingButton from '@mui/lab/LoadingButton'; import AudienceLayer from './Layer/AudienceLayer'; import { iOS, isClientSideRendering, random, stripHtml } from '@selfcommunity/utils'; import classNames from 'classnames'; import ContentPoll from './Content/ContentPoll'; import { useSnackbar } from 'notistack'; import { useThemeProps } from '@mui/system'; import { extractHashtags } from '../../utils/editor'; import TypeSwitchButtonGroup from './TypeSwitchButtonGroup'; import ContentPost from './Content/ContentPost'; import CategoryLayer from './Layer/CategoryLayer'; import LocationLayer from './Layer/LocationLayer'; import ContentDiscussion from './Content/ContentDiscussion'; import { File, Link, Share } from '../../shared/Media'; import Attributes from './Attributes'; import { PREFIX } from './constants'; import ComposerSkeleton from './Skeleton'; import CloseLayer from './Layer/CloseLayer'; import BackdropScrollDisabled from '../../shared/BackdropScrollDisabled'; import { clearAllBodyScrollLocks } from 'body-scroll-lock'; const DialogTransition = forwardRef(function Transition(props, ref) { return _jsx(Fade, Object.assign({ ref: ref }, props)); }); const classes = { root: `${PREFIX}-root`, ios: `${PREFIX}-ios`, title: `${PREFIX}-title`, types: `${PREFIX}-types`, content: `${PREFIX}-content`, attributes: `${PREFIX}-attributes`, medias: `${PREFIX}-medias`, actions: `${PREFIX}-actions`, layerTransitionRoot: `${PREFIX}-layer-transition-root` }; const Root = styled(Dialog, { name: PREFIX, slot: 'Root' })(() => ({})); const LayerTransitionRoot = styled(Slide, { name: PREFIX, slot: 'LayerTransitionRoot' })(({ theme }) => ({})); const COMPOSER_INITIAL_STATE = { id: null, type: null, title: '', titleError: null, html: '', htmlError: null, categories: [], group: null, event: null, categoriesError: null, groupsError: null, addressing: null, addressingError: null, medias: [], poll: null, location: null, error: null }; const reducer = (state, action) => { switch (action.type) { case 'reset': return Object.assign(Object.assign({}, COMPOSER_INITIAL_STATE), { key: random() }); case 'resetEventFeed': return Object.assign(Object.assign({}, COMPOSER_INITIAL_STATE), { event: state.event, key: random() }); case 'resetGroupFeed': return Object.assign(Object.assign({}, COMPOSER_INITIAL_STATE), { group: state.group, key: random() }); case 'resetCategoryFeed': return Object.assign(Object.assign({}, COMPOSER_INITIAL_STATE), { categories: state.categories, key: random() }); case 'multiple': return Object.assign(Object.assign({}, state), action.value); default: return Object.assign(Object.assign({}, state), { [action.type]: action.value }); } }; /** * > API documentation for the Community-JS Composer component. Learn about the available props and the CSS API. * * * The Composer component contains the logic around the creation of [Post](https://developers.selfcommunity.com/docs/apireference/v2/post/create_a_post) and [Discussion](https://developers.selfcommunity.com/docs/apireference/v2/discussion/create_a_discussion) objects. * * :::info To prevent the editor toolbar and action botton bar being hidden underneath the Virtual Keyboard you need to set the `interactive-widget=resizes-content` on meta viewport. This works on chrome for android. For iOS devices there is a lack of support of this meta so we force the blur event when the user start dragging. [More information](https://bram.us/2021/09/13/prevent-items-from-being-hidden-underneath-the-virtual-keyboard-by-means-of-the-virtualkeyboard-api/) ::: #### Import ```jsx import {Composer} from '@selfcommunity/react-ui'; ``` #### Component Name The name `SCComposer` can be used when providing style overrides in the theme. #### CSS |Rule Name|Global class|Description| |---|---|---| |root|.SCComposer-root|Styles applied to the root element.| |ios|.SCComposer-ios|Styles applied to the root element when the device is ios.| |title|.SCComposer-title|Styles applied to the title element.| |types|.SCComposer-types|Styles applied to the types element.| |content|.SCComposer-content|Styles applied to the content.| |attributes|.SCComposer-attributes|Styles applied to the attributes.| |medias|.SCComposer-medias|Styles applied to the medias.| |actions|.SCComposer-actions|Styles applied to the actions section.| |layerTransitionRoot|.SCComposer-layer-transition-root|Styles applied to the overlay layers (eg. location).| * @param inProps */ export default function Composer(inProps) { // PROPS const props = useThemeProps({ props: inProps, name: PREFIX }); const { feedObjectId = null, feedObjectType = null, feedObject = null, defaultValue = {}, mediaObjectTypes = [File, Link, Share], EditorProps = {}, onClose = null, onSuccess = null, feedType } = props, rest = __rest(props, ["feedObjectId", "feedObjectType", "feedObject", "defaultValue", "mediaObjectTypes", "EditorProps", "onClose", "onSuccess", "feedType"]); // Context const { preferences, features } = useSCPreferences(); const scUserContext = useSCUser(); const { enqueueSnackbar } = useSnackbar(); // HOOKS const theme = useTheme(); const fullScreen = useMediaQuery(theme.breakpoints.down('md'), { noSsr: isClientSideRendering() }); // State variables const [isSubmitting, setIsSubmitting] = useState(false); const [layer, setLayer] = useState(); const [state, dispatch] = useReducer(reducer, Object.assign(Object.assign(Object.assign({}, COMPOSER_INITIAL_STATE), defaultValue), { key: random() })); const { key, id, type, title, titleError, html, categories, event, group, addressing, audience, medias, poll, pollError, location, error } = state; const destructureFeedObject = (_feedObject) => { if (_feedObject.type === SCContributionType.POST) { _feedObject = _feedObject; } else if (_feedObject.type === SCContributionType.DISCUSSION) { _feedObject = _feedObject; } else { _feedObject = _feedObject; } if (feedObject.author.id === scUserContext.user.id) { dispatch({ type: 'multiple', value: { id: _feedObject.id, type: _feedObject.poll ? COMPOSER_TYPE_POLL : feedObject.type, title: _feedObject.title, html: _feedObject.html, categories: _feedObject.categories, event: _feedObject.event, group: _feedObject.group, addressing: _feedObject.addressing, medias: _feedObject.medias, poll: _feedObject.poll, location: _feedObject.location } }); setIsLoading(false); } else { setLoadError(true); } }; // Edit state variables const editMode = useMemo(() => Boolean((feedObjectId && feedObjectType) || feedObject), [feedObjectId, feedObjectType, feedObject]); const [isLoading, setIsLoading] = useState(editMode); const [loadError, setLoadError] = useState(false); // REFS const dialogRef = useRef(); const unloadRef = useRef(false); const pointerStartY = useRef(null); // Create a ref for medias because of state update error on chunk upload const mediasRef = useRef({ medias }); mediasRef.current = { medias }; // MEMO const hasPoll = useMemo(() => { return poll && poll.title.length > 0 && poll.title.length < COMPOSER_TITLE_MAX_LENGTH && poll.choices.length >= COMPOSER_POLL_MIN_CHOICES; }, [poll]); const canSubmit = useMemo(() => { return (!isLoading && ((type === SCContributionType.DISCUSSION && title.length > 0 && title.length < COMPOSER_TITLE_MAX_LENGTH) || (type === SCContributionType.POST && (stripHtml(html).length > 0 || medias.length > 0 || hasPoll)) || (type === COMPOSER_TYPE_POLL && hasPoll))); }, [isLoading, type, title, html, medias, hasPoll]); const isIOS = useMemo(() => iOS(), []); // Load feed object useEffect(() => { if (!editMode) { return; } else if (feedObject !== null) { destructureFeedObject(feedObject); return; } setIsLoading(true); http .request({ url: Endpoints.FeedObject.url({ type: feedObjectType, id: feedObjectId }), method: Endpoints.FeedObject.method }) .then((res) => { destructureFeedObject(res.data); }) .catch(() => { setLoadError(true); }) .then(() => setIsLoading(false)); }, [editMode]); // Prevent unload useEffect(() => { if (!unloadRef.current && canSubmit) { unloadRef.current = true; const onUnload = (e) => { e.preventDefault(); return ''; }; window.onbeforeunload = onUnload; } else if (unloadRef.current && !canSubmit) { unloadRef.current = false; window.onbeforeunload = null; } }, [state, canSubmit]); /** * On iOS, since it is not possible to anchor meadiaObject actions * to the bottom of the viewport, detect 'pan' gesture to close the * soft keyboard on device and show actions */ useEffect(() => { if (!dialogRef.current || !isIOS) { return; } /** * On touchStart event save the initial Y * @param e */ const handleTouchStart = (e) => { pointerStartY.current = e.touches[0].clientY; }; /** * Perform blur only if gesture is a pan (bottom direction) * @param e */ const handleTouchmove = (e) => { const currentY = e.touches[0].clientY; const deltaY = currentY - pointerStartY.current; pointerStartY.current = currentY; if (deltaY > 0) { dialogRef.current.focus(); } }; /** * Attach touchstart, touchmove necessary to detect the pan gesture */ dialogRef.current.addEventListener('touchstart', handleTouchStart); dialogRef.current.addEventListener('touchmove', handleTouchmove); /** * To disable scroll on iOS */ // dialogRef.current && // disableBodyScroll(dialogRef.current, { // allowTouchMove: (el) => { // while (el && el !== document.body) { // if (el.getAttribute('class') !== null && el.getAttribute('class').includes('SCComposer-content')) { // return true; // } // el = el.parentElement; // } // } // }); return () => { var _a, _b; (_a = dialogRef.current) === null || _a === void 0 ? void 0 : _a.removeEventListener('touchstart', handleTouchStart); (_b = dialogRef.current) === null || _b === void 0 ? void 0 : _b.removeEventListener('touchmove', handleTouchmove); /** * To re-enable scroll on iOS */ // dialogRef.current && enableBodyScroll(dialogRef.current); }; }, [dialogRef.current, isIOS]); /* Handlers */ const handleAddLayer = useCallback((layer) => setLayer(layer), []); const handleRemoveLayer = useCallback(() => setLayer(null), []); const handleChangeType = useCallback((value) => { dispatch({ type: 'type', value }); }, []); const handleChangePoll = useCallback((content) => { dispatch({ type: 'multiple', value: Object.assign(Object.assign({}, content), { pollError: content.poll.title.length > COMPOSER_TITLE_MAX_LENGTH ? { titleError: _jsx(FormattedMessage, { id: "ui.composer.title.error.maxlength", defaultMessage: "ui.composer.title.error.maxlength" }) } : null }) }); }, []); const handleChangeDiscussion = useCallback((content) => { dispatch({ type: 'multiple', value: Object.assign(Object.assign({}, content), { titleError: content.title.length > COMPOSER_TITLE_MAX_LENGTH ? (_jsx(FormattedMessage, { id: "ui.composer.title.error.maxlength", defaultMessage: "ui.composer.title.error.maxlength" })) : null }) }); }, []); const handleChangePost = useCallback((content) => { dispatch({ type: 'multiple', value: Object.assign({}, content) }); }, []); const handleChangeCategories = useCallback((value) => { dispatch({ type: 'categories', value }); setLayer(null); }, []); const handleAddCategoryLayer = useCallback(() => handleAddLayer({ name: 'category', Component: CategoryLayer, ComponentProps: { onClose: handleRemoveLayer, onSave: handleChangeCategories, defaultValue: categories } }), [handleAddLayer, handleRemoveLayer, handleChangeCategories, categories]); const handleChangeAudience = useCallback((value) => { if (group || (value && Object.prototype.hasOwnProperty.call(value, 'emotional_image_position'))) { dispatch({ type: 'group', value }); } else if (event || (value && Object.prototype.hasOwnProperty.call(value, 'recurring'))) { dispatch({ type: 'event', value }); } else { dispatch({ type: 'addressing', value }); } setLayer(null); }, [group]); const handleAddAudienceLayer = useCallback(() => handleAddLayer({ name: 'audience', Component: AudienceLayer, ComponentProps: { onClose: handleRemoveLayer, onSave: handleChangeAudience, defaultValue: group || (addressing && Object.prototype.hasOwnProperty.call(addressing, 'emotional_image_position')) ? group : event || (addressing && Object.prototype.hasOwnProperty.call(addressing, 'recurring')) ? event : addressing } }), [handleAddLayer, handleRemoveLayer, handleChangeAudience, addressing, event, group]); const handleChangeLocation = useCallback((value) => { dispatch({ type: 'location', value }); setLayer(null); }, []); const handleAddLocationLayer = useCallback(() => handleAddLayer({ name: 'location', Component: LocationLayer, ComponentProps: { onClose: handleRemoveLayer, onSave: handleChangeLocation, defaultValue: location } }), [handleAddLayer, handleRemoveLayer, handleChangeLocation, location]); const handleChangeMedias = useCallback((value) => { const _medias = [...value]; dispatch({ type: 'medias', value: [...value] }); mediasRef.current.medias = _medias; setLayer(null); }, []); const handleAddMedia = useCallback((media) => { const _medias = [...mediasRef.current.medias, media]; dispatch({ type: 'medias', value: _medias }); mediasRef.current.medias = _medias; }, []); const handleMediaTriggerClick = useCallback((mediaObjectType) => (e) => { if (mediaObjectType.layerComponent) { handleAddLayer({ name: mediaObjectType.name, Component: mediaObjectType.layerComponent, ComponentProps: { onClose: handleRemoveLayer, onSave: handleChangeMedias, defaultValue: medias } }); } }, [handleAddLayer, handleRemoveLayer, handleChangeMedias, medias]); const handleChangeAttributes = useCallback((content) => { dispatch({ type: 'multiple', value: Object.assign({}, content) }); }, []); const handleClickAttributes = useCallback((attr) => { switch (attr) { case 'categories': handleAddCategoryLayer(); break; case 'addressing': handleAddAudienceLayer(); break; case 'location': handleAddLocationLayer(); break; } }, [handleAddCategoryLayer, handleAddAudienceLayer, handleAddLocationLayer]); const handleSubmit = useCallback((e) => { e.preventDefault(); e.stopPropagation(); if (UserUtils.isBlocked(scUserContext.user)) { // deny submit action if authenticated user is blocked enqueueSnackbar(_jsx(FormattedMessage, { id: "ui.common.userBlocked", defaultMessage: "ui.common.userBlocked" }), { variant: 'warning', autoHideDuration: 3000 }); return; } // Extract hashtags and add to categories const _categories = [...categories.map((c) => c.id), ...extractHashtags(html)]; const data = { title, text: html, medias: medias.map((m) => m.id), categories: _categories.filter((item, index) => _categories.indexOf(item) === index) }; if ((preferences[SCPreferences.ADDONS_POLLS_ENABLED].value || UserUtils.isStaff(scUserContext.user)) && hasPoll) { data.poll = poll; } if (preferences[SCPreferences.ADDONS_POST_GEOLOCATION_ENABLED].value && location) { data.location = location; } if (features.includes(SCFeatureName.TAGGING) && addressing !== null) { data.addressing = addressing.map((t) => t.id); } if (features.includes(SCFeatureName.TAGGING) && features.includes(SCFeatureName.GROUPING) && preferences[SCPreferences.CONFIGURATIONS_GROUPS_ENABLED].value && group !== null) { data.group = group.id; } if (features.includes(SCFeatureName.TAGGING) && features.includes(SCFeatureName.EVENT) && preferences[SCPreferences.CONFIGURATIONS_EVENTS_ENABLED].value && event !== null) { data.event = event.id; } setIsSubmitting(true); // Finding right url const _type = type === COMPOSER_TYPE_POLL ? SCContributionType.POST : type; let url = Endpoints.Composer.url({ type: _type }); let method = Endpoints.Composer.method; if (editMode) { url = Endpoints.ComposerEdit.url({ type: feedObjectType ? feedObjectType : _type, id }); method = Endpoints.ComposerEdit.method; } // Perform request http .request({ url, method, data }) .then((res) => { onSuccess(res.data); if (unloadRef.current) { window.onbeforeunload = null; } feedType && feedType === SCFeedTypologyType.CATEGORY ? dispatch({ type: 'resetCategoryFeed' }) : feedType === SCFeedTypologyType.EVENT ? dispatch({ type: 'resetEventFeed' }) : feedType === SCFeedTypologyType.GROUP ? dispatch({ type: 'resetGroupFeed' }) : dispatch({ type: 'reset' }); }) .catch((error) => { dispatch({ type: 'multiple', value: formatHttpErrorCode(error) }); }) .then(() => setIsSubmitting(false)); }, [scUserContext.user, feedObjectType, id, type, title, html, categories, event, group, addressing, audience, medias, poll, location, hasPoll]); //edited here const handleClose = useCallback((e, reason) => { if (unloadRef.current) { window.onbeforeunload = null; } if (reason && canSubmit) { handleAddLayer({ name: 'close', Component: CloseLayer, ComponentProps: { onClose: handleRemoveLayer, onSave: () => { onClose && onClose(e); setLayer(null); feedType && feedType === SCFeedTypologyType.CATEGORY ? dispatch({ type: 'resetCategoryFeed' }) : feedType === SCFeedTypologyType.EVENT ? dispatch({ type: 'resetEventFeed' }) : feedType === SCFeedTypologyType.GROUP ? dispatch({ type: 'resetGroupFeed' }) : dispatch({ type: 'reset' }); } } }); } else { clearAllBodyScrollLocks(); onClose && onClose(e); setLayer(null); dispatch({ type: 'reset' }); /*setLayer(null); feedType && feedType === SCFeedTypologyType.CATEGORY ? dispatch({type: 'resetCategoryFeed'}) : feedType === SCFeedTypologyType.GROUP ? dispatch({type: 'resetGroupFeed'}) : dispatch({type: 'reset'}); */ } }, [onClose, canSubmit, handleRemoveLayer]); const handleClosePrompt = useCallback((e) => { if (canSubmit) { handleAddLayer({ name: 'close', Component: CloseLayer, ComponentProps: { onClose: handleRemoveLayer, onSave: handleClose } }); } else { handleClose(e); } }, [canSubmit, handleAddLayer, handleRemoveLayer, handleClose]); // RENDER const hasMediaShare = useMemo(() => medias.findIndex((m) => m.type === MEDIA_TYPE_SHARE) !== -1, [medias]); const content = useMemo(() => { if (editMode && isLoading) { return _jsx(ComposerSkeleton, {}); } else if (editMode && loadError) { return (_jsxs(Alert, Object.assign({ severity: "error", onClose: handleClose }, { children: [_jsx(AlertTitle, { children: _jsx(FormattedMessage, { id: "ui.composer.edit.error.title", defaultMessage: "ui.composer.edit.error.title" }) }), _jsx(FormattedMessage, { id: "ui.composer.edit.error.content", defaultMessage: "ui.composer.edit.error.content" })] }))); } switch (type) { case COMPOSER_TYPE_POLL: return (_jsx(ContentPoll, { onChange: handleChangePoll, value: { html, event, group, addressing, medias, poll, location }, error: pollError, disabled: isSubmitting }, key)); case SCContributionType.DISCUSSION: return (_jsx(ContentDiscussion, { value: { title, html, categories, event, group, addressing, medias, poll, location }, error: { titleError, error }, onChange: handleChangeDiscussion, disabled: isSubmitting, isContentSwitchButtonVisible: !canSubmit && !editMode, EditorProps: Object.assign({ toolbar: true, uploadImage: true }, EditorProps) }, key)); default: return (_jsx(ContentPost, { value: { html, categories, event, group, addressing, medias, poll, location }, error: { error }, onChange: handleChangePost, disabled: isSubmitting, EditorProps: Object.assign({ toolbar: false, uploadImage: false }, EditorProps) }, key)); } }, [ key, type, title, html, categories, event, group, addressing, medias, poll, pollError, location, error, handleChangePoll, handleChangePost, isSubmitting, canSubmit, editMode ]); if (!scUserContext.user && !(scUserContext.loading && open)) { return null; } return (_jsxs(Root, Object.assign({ ref: dialogRef, TransitionComponent: DialogTransition, slots: { backdrop: BackdropScrollDisabled }, onClose: handleClose }, rest, { disableEscapeKeyDown: true, className: classNames(classes.root, { [classes.ios]: isIOS }), scroll: "body", fullScreen: fullScreen, tabIndex: -1 }, { children: [_jsxs("form", Object.assign({ onSubmit: handleSubmit, method: "post" }, { children: [_jsxs(DialogTitle, Object.assign({ className: classes.title }, { children: [_jsx(IconButton, Object.assign({ onClick: handleClosePrompt }, { children: _jsx(Icon, { children: "close" }) })), _jsx(LoadingButton, Object.assign({ size: "small", type: "submit", color: "secondary", variant: "contained", disabled: !canSubmit, loading: isSubmitting }, { children: _jsx(FormattedMessage, { id: "ui.composer.submit", defaultMessage: "ui.composer.submit" }) }))] })), _jsxs(DialogContent, Object.assign({ className: classes.content }, { children: [_jsx(Attributes, { value: { categories, event, group, addressing, location }, className: classes.attributes, onChange: handleChangeAttributes, onClick: handleClickAttributes }), content, medias && medias.length > 0 && (_jsx(Box, Object.assign({ className: classes.medias }, { children: mediaObjectTypes.map((mediaObjectType) => { if (mediaObjectType.previewComponent) { return _jsx(mediaObjectType.previewComponent, { value: medias, onChange: handleChangeMedias }, mediaObjectType.name); } else if (mediaObjectType.displayComponent) { return _jsx(mediaObjectType.displayComponent, { medias: medias }, mediaObjectType.name); } }) })))] })), !canSubmit && !editMode && _jsx(TypeSwitchButtonGroup, { className: classes.types, onChange: handleChangeType, size: "small", value: type }), _jsxs(DialogActions, Object.assign({ className: classes.actions }, { children: [mediaObjectTypes .filter((mediaObjectType) => mediaObjectType.triggerButton !== null) .map((mediaObjectType) => { const props = mediaObjectType.layerComponent ? { onClick: handleMediaTriggerClick(mediaObjectType) } : { onAdd: handleAddMedia }; return (_jsx(mediaObjectType.triggerButton, Object.assign({ disabled: isSubmitting || hasMediaShare, color: medias.filter(mediaObjectType.filter).length > 0 ? 'primary' : 'default' }, props), mediaObjectType.name)); }), _jsx(IconButton, Object.assign({ disabled: isSubmitting, onClick: handleAddCategoryLayer }, { children: _jsx(Icon, { children: "category" }) })), _jsx(IconButton, Object.assign({ disabled: isSubmitting || !features.includes(SCFeatureName.TAGGING) || Boolean(feedObject === null || feedObject === void 0 ? void 0 : feedObject.group) || Boolean(feedObject === null || feedObject === void 0 ? void 0 : feedObject.event), onClick: handleAddAudienceLayer }, { children: (!group && addressing === null) || (!event && addressing === null) || (addressing === null || addressing === void 0 ? void 0 : addressing.length) === 0 ? (_jsx(Icon, { children: "public" })) : group ? (_jsx(Icon, { children: "groups" })) : event ? (_jsx(Icon, { children: "CalendarIcon" })) : (_jsx(Icon, { children: "label" })) })), preferences[SCPreferences.ADDONS_POST_GEOLOCATION_ENABLED].value && (_jsx(IconButton, Object.assign({ disabled: isSubmitting, onClick: handleAddLocationLayer, color: location !== null ? 'primary' : 'default' }, { children: _jsx(Icon, { children: "add_location_alt" }) })))] }))] })), layer && (_jsx(LayerTransitionRoot, Object.assign({ className: classes.layerTransitionRoot, in: true, container: dialogRef.current, direction: "left" }, { children: _jsx(layer.Component, Object.assign({}, layer.ComponentProps)) })))] }))); }