Add animation to group open

This commit is contained in:
Mitchell McCaffrey 2021-05-24 17:11:46 +10:00
parent 0917ef05a1
commit 4e5bd8bf02
12 changed files with 170 additions and 76 deletions

View File

@ -16,9 +16,13 @@ function MapTileGroup({ group, maps, isSelected, onSelect, onDoubleClick }) {
onSelect={() => onSelect(group.id)} onSelect={() => onSelect(group.id)}
onDoubleClick={onDoubleClick} onDoubleClick={onDoubleClick}
> >
<Grid columns={layout.gridTemplate} p={2} sx={{ gridGap: 2 }}> <Grid columns={layout.groupGridTemplate} p={2} sx={{ gridGap: 2 }}>
{maps.slice(0, 16).map((map) => ( {maps.slice(0, 16).map((map) => (
<MapTileImage sx={{ borderRadius: "8px" }} map={map} key={map.id} /> <MapTileImage
sx={{ borderRadius: "8px" }}
map={map}
key={`${map.id}-group-tile`}
/>
))} ))}
</Grid> </Grid>
</Tile> </Tile>

View File

@ -13,6 +13,7 @@ function MapTiles({ maps, onMapEdit, onMapSelect, subgroup }) {
const { const {
groups, groups,
selectedGroupIds, selectedGroupIds,
openGroupId,
openGroupItems, openGroupItems,
selectMode, selectMode,
onGroupOpen, onGroupOpen,
@ -63,6 +64,7 @@ function MapTiles({ maps, onMapEdit, onMapSelect, subgroup }) {
renderTile={renderTile} renderTile={renderTile}
onTileSelect={onGroupSelect} onTileSelect={onGroupSelect}
disableGrouping={subgroup} disableGrouping={subgroup}
openGroupId={openGroupId}
/> />
); );
} }

View File

@ -2,8 +2,9 @@ import React from "react";
import { Box } from "theme-ui"; import { Box } from "theme-ui";
import { useDroppable } from "@dnd-kit/core"; import { useDroppable } from "@dnd-kit/core";
import { useSortable } from "@dnd-kit/sortable"; import { useSortable } from "@dnd-kit/sortable";
import { animated, useSpring } from "react-spring";
function Sortable({ id, disableGrouping, children }) { function Sortable({ id, disableGrouping, hidden, children }) {
const { const {
attributes, attributes,
listeners, listeners,
@ -48,8 +49,10 @@ function Sortable({ id, disableGrouping, children }) {
over?.id === `__group__${id}` && active.id !== id ? "solid" : "none", over?.id === `__group__${id}` && active.id !== id ? "solid" : "none",
}; };
const { opacity } = useSpring({ opacity: hidden ? 0 : 1 });
return ( return (
<Box sx={{ position: "relative" }}> <animated.div style={{ opacity, position: "relative" }}>
<Box <Box
ref={setDraggableNodeRef} ref={setDraggableNodeRef}
style={dragStyle} style={dragStyle}
@ -75,7 +78,7 @@ function Sortable({ id, disableGrouping, children }) {
sx={{ borderColor: "primary" }} sx={{ borderColor: "primary" }}
/> />
</Box> </Box>
</Box> </animated.div>
); );
} }

View File

@ -11,12 +11,9 @@ import {
} from "@dnd-kit/core"; } from "@dnd-kit/core";
import { SortableContext, arrayMove } from "@dnd-kit/sortable"; import { SortableContext, arrayMove } from "@dnd-kit/sortable";
import { animated, useSpring, config } from "react-spring"; import { animated, useSpring, config } from "react-spring";
import { Grid } from "theme-ui";
import { combineGroups, moveGroups } from "../../helpers/group"; import { combineGroups, moveGroups } from "../../helpers/group";
import useResponsiveLayout from "../../hooks/useResponsiveLayout";
import SortableTile from "./SortableTile"; import SortableTile from "./SortableTile";
function SortableTiles({ function SortableTiles({
@ -25,9 +22,8 @@ function SortableTiles({
renderTile, renderTile,
onTileSelect, onTileSelect,
disableGrouping, disableGrouping,
openGroupId,
}) { }) {
const layout = useResponsiveLayout();
const mouseSensor = useSensor(MouseSensor, { const mouseSensor = useSensor(MouseSensor, {
activationConstraint: { delay: 250, tolerance: 5 }, activationConstraint: { delay: 250, tolerance: 5 },
}); });
@ -105,27 +101,16 @@ function SortableTiles({
collisionDetection={closestCenter} collisionDetection={closestCenter}
> >
<SortableContext items={groups}> <SortableContext items={groups}>
<Grid {groups.map((group) => (
p={3} <SortableTile
pb={4} id={group.id}
sx={{ key={group.id}
borderRadius: "4px", disableGrouping={disableGrouping}
overflow: "hidden", hidden={group.id === openGroupId}
}} >
gap={2} {renderSortableGroup(group)}
columns={layout.gridTemplate} </SortableTile>
onClick={() => onTileSelect()} ))}
>
{groups.map((group) => (
<SortableTile
id={group.id}
key={group.id}
disableGrouping={disableGrouping}
>
{renderSortableGroup(group)}
</SortableTile>
))}
</Grid>
{createPortal( {createPortal(
<DragOverlay dropAnimation={null}> <DragOverlay dropAnimation={null}>
{dragId && ( {dragId && (

View File

@ -1,14 +1,40 @@
import React from "react"; import React from "react";
import { Box, Grid } from "theme-ui";
import SimpleBar from "simplebar-react"; import SimpleBar from "simplebar-react";
import { useGroup } from "../../contexts/GroupContext";
import useResponsiveLayout from "../../hooks/useResponsiveLayout"; import useResponsiveLayout from "../../hooks/useResponsiveLayout";
function TilesContainer({ children }) { function TilesContainer({ children }) {
const { onGroupSelect } = useGroup();
const layout = useResponsiveLayout(); const layout = useResponsiveLayout();
return ( return (
<SimpleBar style={{ height: layout.tileContainerHeight }}> <SimpleBar style={{ height: layout.tileContainerHeight }}>
{children} <Box
sx={{
position: "absolute",
width: "100%",
height: "100%",
top: 0,
}}
bg="muted"
onClick={() => onGroupSelect()}
/>
<Grid
p={3}
pb={4}
sx={{
borderRadius: "4px",
overflow: "hidden",
}}
gap={2}
columns={layout.gridTemplate}
>
{children}
</Grid>
</SimpleBar> </SimpleBar>
); );
} }

View File

@ -1,48 +1,111 @@
import React from "react"; import React, { useState } from "react";
import { Box, Close } from "theme-ui"; import { Box, Close, Grid } from "theme-ui";
import { useSpring, animated, config } from "react-spring";
import ReactResizeDetector from "react-resize-detector";
import SimpleBar from "simplebar-react";
import { useGroup } from "../../contexts/GroupContext"; import { useGroup } from "../../contexts/GroupContext";
function TilesOverlay({ children }) { import useResponsiveLayout from "../../hooks/useResponsiveLayout";
const { openGroupId, onGroupClose } = useGroup();
if (!openGroupId) { function TilesOverlay({ children }) {
return null; const { openGroupId, onGroupClose, onGroupSelect } = useGroup();
const openAnimation = useSpring({
opacity: openGroupId ? 1 : 0,
transform: openGroupId ? "scale(1)" : "scale(0.95)",
config: config.gentle,
});
const [containerSize, setContinerSize] = useState(0);
function handleResize(width, height) {
const size = Math.min(width, height) - 16;
setContinerSize(size);
} }
const layout = useResponsiveLayout();
return ( return (
<Box <>
sx={{ {openGroupId && (
position: "absolute", <Box
width: "100%", sx={{
height: "100%", position: "absolute",
top: 0, width: "100%",
cursor: "pointer", height: "100%",
}} top: 0,
p={3} }}
bg="overlay" bg="overlay"
onClick={() => onGroupClose()} />
> )}
<Box <ReactResizeDetector handleWidth handleHeight onResize={handleResize}>
sx={{ <animated.div
width: "100%", style={{
height: "100%", ...openAnimation,
borderRadius: "8px", position: "absolute",
border: "1px solid", width: "100%",
borderColor: "border", height: "100%",
cursor: "default", top: 0,
}} display: "flex",
bg="muted" alignItems: "center",
onClick={(e) => e.stopPropagation()} justifyContent: "center",
p={3} cursor: "pointer",
> pointerEvents: openGroupId ? undefined : "none",
{children} }}
</Box> onClick={() => openGroupId && onGroupClose()}
<Close >
onClick={() => onGroupClose()} <Box
sx={{ position: "absolute", top: "16px", right: "16px" }} sx={{
/> width: containerSize,
</Box> height: containerSize,
borderRadius: "8px",
border: "1px solid",
borderColor: "border",
cursor: "default",
display: "flex",
alignItems: "center",
justifyContent: "center",
position: "relative",
}}
bg="background"
onClick={(e) => e.stopPropagation()}
>
<Box
sx={{
position: "absolute",
width: "100%",
height: "100%",
top: 0,
}}
bg="muted"
onClick={() => onGroupSelect()}
/>
<SimpleBar
style={{
width: containerSize - 16,
height: containerSize - 48,
}}
>
<Grid
sx={{
borderRadius: "4px",
overflow: "hidden",
}}
gap={2}
columns={layout.groupGridTemplate}
px={3}
>
{children}
</Grid>
</SimpleBar>
<Close
onClick={() => onGroupClose()}
sx={{ position: "absolute", top: 0, right: 0 }}
/>
</Box>
</animated.div>
</ReactResizeDetector>
</>
); );
} }

View File

@ -57,7 +57,7 @@ function TokenBar({ onMapTokenStateCreate }) {
height: "100%", height: "100%",
width: "80px", width: "80px",
minWidth: "80px", minWidth: "80px",
overflowY: "scroll", overflowY: "hidden",
overflowX: "hidden", overflowX: "hidden",
display: fullScreen ? "none" : "block", display: fullScreen ? "none" : "block",
}} }}

View File

@ -23,12 +23,12 @@ function TokenTileGroup({
onDoubleClick={onDoubleClick} onDoubleClick={onDoubleClick}
columns="1fr 1fr" columns="1fr 1fr"
> >
<Grid columns={layout.gridTemplate} p={2} sx={{ gridGap: 2 }}> <Grid columns={layout.groupGridTemplate} p={2} sx={{ gridGap: 2 }}>
{tokens.slice(0, 16).map((token) => ( {tokens.slice(0, 16).map((token) => (
<TokenTileImage <TokenTileImage
sx={{ borderRadius: "8px" }} sx={{ borderRadius: "8px" }}
token={token} token={token}
key={token.id} key={`${token.id}-group-tile`}
/> />
))} ))}
</Grid> </Grid>

View File

@ -13,6 +13,7 @@ function TokenTiles({ tokens, onTokenEdit, subgroup }) {
const { const {
groups, groups,
selectedGroupIds, selectedGroupIds,
openGroupId,
openGroupItems, openGroupItems,
selectMode, selectMode,
onGroupOpen, onGroupOpen,
@ -68,6 +69,7 @@ function TokenTiles({ tokens, onTokenEdit, subgroup }) {
renderTile={renderTile} renderTile={renderTile}
onTileSelect={onGroupSelect} onTileSelect={onGroupSelect}
disableGrouping={subgroup} disableGrouping={subgroup}
openGroupId={openGroupId}
/> />
); );
} }

View File

@ -27,9 +27,18 @@ function useResponsiveLayout() {
? "1fr 1fr 1fr" ? "1fr 1fr 1fr"
: "1fr 1fr"; : "1fr 1fr";
const groupGridTemplate = isLargeScreen ? "1fr 1fr 1fr" : "1fr 1fr";
const tileContainerHeight = isLargeScreen ? "600px" : "400px"; const tileContainerHeight = isLargeScreen ? "600px" : "400px";
return { screenSize, modalSize, tileSize, gridTemplate, tileContainerHeight }; return {
screenSize,
modalSize,
tileSize,
gridTemplate,
tileContainerHeight,
groupGridTemplate,
};
} }
export default useResponsiveLayout; export default useResponsiveLayout;

View File

@ -252,7 +252,7 @@ function SelectMapModal({
<Label pt={2} pb={1}> <Label pt={2} pb={1}>
Select or import a map Select or import a map
</Label> </Label>
<Box sx={{ position: "relative" }} bg="muted"> <Box sx={{ position: "relative" }}>
<GroupProvider <GroupProvider
groups={mapGroups} groups={mapGroups}
onGroupsChange={updateMapGroups} onGroupsChange={updateMapGroups}

View File

@ -193,7 +193,7 @@ function SelectTokensModal({ isOpen, onRequestClose }) {
<Label pt={2} pb={1}> <Label pt={2} pb={1}>
Edit or import a token Edit or import a token
</Label> </Label>
<Box sx={{ position: "relative" }} bg="muted"> <Box sx={{ position: "relative" }}>
<GroupProvider <GroupProvider
groups={tokenGroups} groups={tokenGroups}
onGroupsChange={updateTokenGroups} onGroupsChange={updateTokenGroups}