File manager - Edit - /var/www/payraty/helpdesk/common/resources/client/admin/appearance/sections/menus/menu-editor.tsx
Back
import { FieldArrayWithId, useFieldArray, UseFieldArrayReturn, useFormContext, } from 'react-hook-form'; import {Fragment, useEffect, useMemo, useRef} from 'react'; import {Link, useNavigate, useParams} from 'react-router-dom'; import {MenuSectionConfig} from '../../types/appearance-editor-config'; import {MenuItemConfig} from '@common/core/settings/settings'; import { appearanceState, AppearanceValues, useAppearanceStore, } from '../../appearance-store'; import {FormTextField} from '@common/ui/forms/input-field/text-field/text-field'; import {Button} from '@common/ui/buttons/button'; import {AddMenuItemDialog} from '@common/admin/appearance/sections/menus/add-menu-item-dialog'; import {AppearanceButton} from '@common/admin/appearance/appearance-button'; import {AddIcon} from '@common/icons/material/Add'; import {DragIndicatorIcon} from '@common/icons/material/DragIndicator'; import {ConfirmationDialog} from '@common/ui/overlays/dialog/confirmation-dialog'; import {IllustratedMessage} from '@common/ui/images/illustrated-message'; import {SvgImage} from '@common/ui/images/svg-image/svg-image'; import {DeleteIcon} from '@common/icons/material/Delete'; import {DialogTrigger} from '@common/ui/overlays/dialog/dialog-trigger'; import {Option} from '../../../../ui/forms/select/select'; import {Trans} from '@common/i18n/trans'; import dropdownMenu from './dropdown-menu.svg'; import {FormChipField} from '@common/ui/forms/input-field/chip-field/form-chip-field'; import { useSortable, UseSortableProps, } from '@common/ui/interactions/dnd/sortable/use-sortable'; import {IconButton} from '@common/ui/buttons/icon-button'; import {createSvgIconFromTree} from '@common/icons/create-svg-icon'; import {useSettings} from '@common/core/settings/use-settings'; export function MenuEditor() { const {menuIndex} = useParams(); const navigate = useNavigate(); const {getValues} = useFormContext<AppearanceValues>(); const formPath = `settings.menus.${menuIndex!}` as 'settings.menus.0'; const menu = getValues(formPath); useEffect(() => { // go to menu list, if menu can't be found if (!menu) { navigate('/admin/appearance/menus'); } else { appearanceState().preview.setHighlight(`[data-menu-id="${menu.id}"]`); } }, [navigate, menu]); if (!menu) { return null; } return <MenuEditorSection formPath={formPath} />; } interface MenuEditorFormProps { formPath: 'settings.menus.0'; } function MenuEditorSection({formPath}: MenuEditorFormProps) { const { site: {has_mobile_app}, } = useSettings(); const menuSectionConfig = useAppearanceStore( s => s.config?.sections.menus.config, ) as MenuSectionConfig; const menuPositions = useMemo(() => { const positions = [...menuSectionConfig?.positions]; if (has_mobile_app) { positions.push('mobile-app-about'); } return positions.map(position => ({ key: position, name: position.replaceAll('-', ' '), })); }, [menuSectionConfig, has_mobile_app]); const fieldArray = useFieldArray< AppearanceValues, `settings.menus.0.items`, 'key' >({ name: `${formPath}.items`, keyName: 'key', }); return ( <Fragment> <div className="mb-30 border-b pb-30"> <FormTextField name={`${formPath}.name`} label={<Trans message="Menu name" />} className="mb-20" autoFocus /> <FormChipField chipSize="sm" name={`${formPath}.positions`} valueKey="id" label={<Trans message="Menu positions" />} description={ <Trans message="Where should this menu appear on the site" /> } > {menuPositions.map(item => ( <Option key={item.key} value={item.key} capitalizeFirst> {item.name} </Option> ))} </FormChipField> </div> <MenuItemsManager fieldArray={fieldArray} /> <div className="text-right"> <DeleteMenuTrigger /> </div> </Fragment> ); } interface ItemListProps { fieldArray: UseFieldArrayReturn< AppearanceValues, 'settings.menus.0.items', 'key' >; } function MenuItemsManager({fieldArray: {append, fields, move}}: ItemListProps) { const navigate = useNavigate(); return ( <Fragment> <div className="flex flex-shrink-0 items-center justify-between gap-16"> <Trans message="Menu items" /> <DialogTrigger type="popover" placement="right" offset={20} onClose={(menuItemConfig?: MenuItemConfig) => { if (menuItemConfig) { append({...menuItemConfig}); navigate(`items/${fields.length}`); } }} > <Button variant="outline" color="primary" size="xs" startIcon={<AddIcon />} > <Trans message="Add" /> </Button> <AddMenuItemDialog /> </DialogTrigger> </div> <div className="mt-20 flex-shrink-0"> {fields.map((item, index) => ( <MenuListItem key={item.key} item={item} items={fields} index={index} onSortEnd={(oldIndex, newIndex) => { move(oldIndex, newIndex); }} /> ))} {!fields.length ? ( <IllustratedMessage size="xs" className="my-40" image={<SvgImage src={dropdownMenu} />} title={<Trans message="No menu items yet" />} description={ <Trans message="Click “add“ button to start adding links, pages, routes and other items to this menu. " /> } /> ) : null} </div> </Fragment> ); } function DeleteMenuTrigger() { const navigate = useNavigate(); const {menuIndex} = useParams(); const {fields, remove} = useFieldArray< AppearanceValues, 'settings.menus', 'key' >({ name: 'settings.menus', keyName: 'key', }); if (!menuIndex) return null; const menu = fields[+menuIndex]; return ( <DialogTrigger type="modal" onClose={isConfirmed => { if (isConfirmed) { const index = fields.findIndex(m => m.id === menu.id); remove(index); navigate('/admin/appearance/menus'); } }} > <Button variant="outline" color="danger" size="xs" startIcon={<DeleteIcon />} > <Trans message="Delete menu" /> </Button> <ConfirmationDialog isDanger title={<Trans message="Delete menu" />} body={ <Trans message="Are you sure you want to delete “:name“?" values={{name: menu.name}} /> } confirm={<Trans message="Delete" />} /> </DialogTrigger> ); } interface MenuListItemProps { item: MenuItemConfig; items: FieldArrayWithId[]; index: number; onSortEnd: UseSortableProps['onSortEnd']; } function MenuListItem({item, items, index, onSortEnd}: MenuListItemProps) { const ref = useRef<HTMLButtonElement>(null); const {sortableProps, dragHandleRef} = useSortable({ item, items, type: 'menuEditorSortable', ref, onSortEnd, strategy: 'liveSort', }); const Icon = item.icon && createSvgIconFromTree(item.icon); const iconOnlyLabel = ( <div className="flex items-center gap-4 text-xs text-muted"> {Icon && <Icon size="sm" />} (<Trans message="No label..." />) </div> ); return ( <Fragment> <AppearanceButton elementType={Link} to={`items/${index}`} ref={ref} {...sortableProps} > <div className="flex items-center gap-10"> <IconButton ref={dragHandleRef} size="sm"> <DragIndicatorIcon className="text-muted hover:cursor-move" /> </IconButton> <div>{item.label || iconOnlyLabel}</div> </div> </AppearanceButton> </Fragment> ); }
| ver. 1.4 |
Github
|
.
| PHP 8.3.30 | Generation time: 0 |
proxy
|
phpinfo
|
Settings