UNPKG

@steambrew/client

Version:
91 lines (90 loc) 4.8 kB
import { Fragment, useEffect, useState } from 'react'; import { Field, Focusable, GamepadButton } from '../components'; /** * A component for creating reorderable lists. * * See an example implementation {@linkplain https://github.com/Tormak9970/Component-Testing-Plugin/blob/main/src/testing-window/ReorderableListTest.tsx here}. */ export function ReorderableList(props) { if (props.animate === undefined) props.animate = true; const [entryList, setEntryList] = useState([...props.entries].sort((a, b) => a.position - b.position)); const [reorderEnabled, setReorderEnabled] = useState(false); useEffect(() => { setEntryList([...props.entries].sort((a, b) => a.position - b.position)); }, [props.entries]); function toggleReorderEnabled() { let newReorderValue = !reorderEnabled; setReorderEnabled(newReorderValue); if (!newReorderValue) { props.onSave(entryList); } } function saveOnBackout(e) { const event = e; if (event.detail.button == GamepadButton.CANCEL && reorderEnabled) { setReorderEnabled(!reorderEnabled); props.onSave(entryList); } } return (window.SP_REACT.createElement(Fragment, null, window.SP_REACT.createElement("div", { style: { width: 'inherit', height: 'inherit', flex: '1 1 1px', scrollPadding: '48px 0px', display: 'flex', flexDirection: 'column', justifyContent: 'flex-start', alignContent: 'stretch', } }, window.SP_REACT.createElement(Focusable, { onSecondaryButton: toggleReorderEnabled, onSecondaryActionDescription: reorderEnabled ? 'Save Order' : 'Reorder', onClick: toggleReorderEnabled, onButtonDown: saveOnBackout }, entryList.map((entry) => (window.SP_REACT.createElement(ReorderableItem, { animate: props.animate, listData: entryList, entryData: entry, reorderEntryFunc: setEntryList, reorderEnabled: reorderEnabled, fieldProps: props.fieldProps }, props.interactables ? window.SP_REACT.createElement(props.interactables, { entry: entry }) : null))))))); } function ReorderableItem(props) { const [isSelected, _setIsSelected] = useState(false); const [isSelectedLastFrame, setIsSelectedLastFrame] = useState(false); const listEntries = props.listData; function onReorder(e) { if (!props.reorderEnabled) return; const event = e; const currentIdx = listEntries.findIndex((entryData) => entryData === props.entryData); const currentIdxValue = listEntries[currentIdx]; if (currentIdx < 0) return; let targetPosition = -1; if (event.detail.button == GamepadButton.DIR_DOWN) { targetPosition = currentIdxValue.position + 1; } else if (event.detail.button == GamepadButton.DIR_UP) { targetPosition = currentIdxValue.position - 1; } if (targetPosition >= listEntries.length || targetPosition < 0) return; let otherToUpdate = listEntries.find((entryData) => entryData.position === targetPosition); if (!otherToUpdate) return; let currentPosition = currentIdxValue.position; currentIdxValue.position = otherToUpdate.position; otherToUpdate.position = currentPosition; props.reorderEntryFunc([...listEntries].sort((a, b) => a.position - b.position)); } async function setIsSelected(val) { _setIsSelected(val); // Wait 3 frames, then set. I have no idea why, but if you dont wait long enough it doesn't work. for (let i = 0; i < 3; i++) await new Promise((res) => requestAnimationFrame(res)); setIsSelectedLastFrame(val); } return (window.SP_REACT.createElement("div", { style: props.animate ? { transition: isSelected || isSelectedLastFrame ? '' : 'transform 0.3s cubic-bezier(0.25, 1, 0.5, 1), opacity 0.3s cubic-bezier(0.25, 1, 0.5, 1)', transform: !props.reorderEnabled || isSelected ? 'scale(1)' : 'scale(0.9)', opacity: !props.reorderEnabled || isSelected ? 1 : 0.7, } : {} }, window.SP_REACT.createElement(Field, { label: props.entryData.label, ...props.fieldProps, focusable: !props.children, onButtonDown: onReorder, onGamepadBlur: () => setIsSelected(false), onGamepadFocus: () => setIsSelected(true) }, window.SP_REACT.createElement(Focusable, { style: { display: 'flex', width: '100%', position: 'relative' } }, props.children)))); }