Skip to content

Commit

Permalink
feat(app-board): add zoom fab dial
Browse files Browse the repository at this point in the history
  • Loading branch information
rams23 committed Jan 19, 2021
1 parent e58c91b commit c55c1e5
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 79 deletions.
74 changes: 74 additions & 0 deletions packages/game-app/src/_shared/components/FabDial/FabDial.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, { useState } from 'react';
import IconButton from '../IconButton';
import styled, { css } from 'styled-components';

type Props = {
className?: string;
icon: React.ReactNode;

buttons: {
icon: React.ReactNode;
onClick: () => void;
}[];
};

const ButtonsWrapper = styled.div<{ isOpen: boolean }>`
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 0;
${IconButton} {
transition: transform 0.5s;
position: absolute;
top: 0;
transform: translate(0, 0);
}
${props =>
props.isOpen &&
css`
${IconButton}:nth-child(3) {
transform: translate(0, -48px);
}
${IconButton}:nth-child(2) {
transform: translate(0, -96px);
}
${IconButton}:nth-child(1) {
transform: translate(0, -144px);
}
`}
`;

const MainButtonIcon = styled(IconButton)`
position: absolute;
z-index: 1;
`;

const FabDial: React.FC<Props> = ({ icon, buttons, className }) => {
const [open, setOpen] = useState(false);

return (
<div style={{ position: 'relative', width: '40px', height: '40px' }} className={className}>
<MainButtonIcon onClick={() => setOpen(s => !s)} variant="rounded">
{icon}
</MainButtonIcon>
<ButtonsWrapper isOpen={open}>
{buttons.map((b, index) => (
<IconButton key={index} variant="rounded" onClick={b.onClick}>
{b.icon}
</IconButton>
))}
</ButtonsWrapper>
</div>
);
};

FabDial.displayName = 'FabDial';

export default FabDial;
3 changes: 3 additions & 0 deletions packages/game-app/src/_shared/components/FabDial/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import FabDial from './FabDial';

export default FabDial;
38 changes: 30 additions & 8 deletions packages/game-app/src/_shared/components/IconButton/IconButton.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import React from 'react';
import styled from 'styled-components';
import styled, { css } from 'styled-components';
import { variant } from 'styled-system';

type IconButtonVariants = 'default' | 'rounded';

type Props = {
onClick: () => void;
className?: string;
active?: boolean;
variant?: IconButtonVariants;
};

const StyledButton = styled.button<{ active?: boolean }>`
const StyledButton = styled.button<{ active?: boolean; variant: IconButtonVariants }>`
background-color: transparent;
border: none;
padding: 8px;
Expand All @@ -16,6 +20,7 @@ const StyledButton = styled.button<{ active?: boolean }>`
width: 40px;
height: 40px;
box-sizing: border-box;
cursor: pointer;
&:hover {
color: #00867c;
Expand All @@ -28,15 +33,32 @@ const StyledButton = styled.button<{ active?: boolean }>`
${props =>
props.active &&
`
background: #EEEEEE;
color: #00867C;
`}
css`
background: #eeeeee;
color: #00867c;
`} ${variant({
variants: {
rounded: {
borderRadius: '50%',
background: '#FFFFFF',
'&> *:first-child': {
width: '16px',
height: '16px',
transition: 'transform 0.3s',
},
':hover': {
'&> *:first-child': {
transform: 'rotate(20deg) scale(1.2)',
},
},
},
},
})}
`;

const IconButton: React.FC<Props> = ({ children, onClick, className, active }) => {
const IconButton: React.FC<Props> = ({ children, onClick, className, active, variant = 'default' }) => {
return (
<StyledButton type="button" onClick={onClick} className={className} active={active}>
<StyledButton type="button" variant={variant} onClick={onClick} className={className} active={active}>
{children}
</StyledButton>
);
Expand Down
2 changes: 2 additions & 0 deletions packages/game-app/src/_shared/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import AnimatedGrid from './AnimatedGrid';
import * as animations from './animations';
import ExpandableTopPanel from './ExpandableTopPanel';
import Typography from './Typography';
import FabDial from './FabDial';

export {
TextInput,
Expand All @@ -26,4 +27,5 @@ export {
animations,
ExpandableTopPanel,
Typography,
FabDial,
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ export const BottomWidgetsRowContainer = styled.div`
display: flex;
flex-direction: row;
align-items: flex-end;
& > * {
margin-left: 50px;
}
`;

export const PoweredByContainer = styled.div`
display: flex;
flex-direction: row;
align-items: center;
margin-left: 50px;
`;

export const TextLogoWrapper = styled.div`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@ import React from 'react';
import { BottomWidgetsRowContainer, PoweredByContainer, TextLogoWrapper } from './BottomWidgetsRow.styled';
import ScenarioPanel from '../ScenarioPanel';
import { ReactComponent as EficodeTextLogo } from '@assets/images/eficode-text-logo.svg';
import { Typography } from '@pipeline/components';
import { ReactComponent as LensIcon } from '@assets/icons/zoom.svg';
import { ReactComponent as ZoomInIcon } from '@assets/icons/zoom-in.svg';
import { ReactComponent as ZoomOutIcon } from '@assets/icons/zoom-out.svg';
import { ReactComponent as FitScreenIcon } from '@assets/icons/fit-screen.svg';
import { FabDial, IconButton, Typography } from '@pipeline/components';

type Props = {};
type Props = {
zoomIn: () => void;
zoomOut: () => void;
fitWindow: () => void;
};

const BottomWidgetsRowStyled: React.FC<Props> = () => {
const BottomWidgetsRowStyled: React.FC<Props> = ({ fitWindow, zoomIn, zoomOut }) => {
return (
<BottomWidgetsRowContainer>
<ScenarioPanel />
Expand All @@ -18,6 +26,26 @@ const BottomWidgetsRowStyled: React.FC<Props> = () => {
<EficodeTextLogo />
</TextLogoWrapper>
</PoweredByContainer>
<FabDial
icon={<LensIcon />}
buttons={[
{
icon: <ZoomInIcon />,
onClick: zoomIn,
},
{
icon: <ZoomOutIcon />,
onClick: zoomOut,
},
{
icon: <FitScreenIcon />,
onClick: fitWindow,
},
]}
/>
<IconButton onClick={() => ({})}>
<ZoomInIcon />
</IconButton>
</BottomWidgetsRowContainer>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { GameEvent, GameEventType } from '../../types/gameEvents';
import { GameUIState } from '../../types/gameUIState';
import ConnectedCard from '../ConnectedCard';

const DEBUG_ENABLED = true;
const DEBUG_ENABLED = false;

const debugPrint = (...data: any[]) => DEBUG_ENABLED && console.debug('[CardsGameListeners]', ...data);

Expand Down
68 changes: 42 additions & 26 deletions packages/game-app/src/gameView/components/GameView/GameView.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback } from 'react';
import React, { useCallback, useMemo } from 'react';
import CardsGameListeners from '../CardsGameListeners';
import { TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch';
import Board from '../Board';
Expand All @@ -12,48 +12,64 @@ import useCardEventHandler from '../../hooks/useCardEventHandler';
import DeckPanel from '../DeckPanel';
import BottomWidgetsRow from '../BottomWidgetsRow/BottomWidgetsRow';

const Game: React.FC<{ pan: { x: number; y: number }; scale: number; gameId: string }> = React.memo(
({ pan, scale, gameId }) => {
const state = useSelector(selectors.getCardStateForUI);
type GameProps = {
pan: { x: number; y: number };
scale: number;
gameId: string;
zoomIn: () => void;
zoomOut: () => void;
fitWindow: () => void;
};

const Game: React.FC<GameProps> = React.memo(({ pan, scale, gameId, fitWindow, zoomIn, zoomOut }) => {
const state = useSelector(selectors.getCardStateForUI);

const { deckCardsIds, placedCardsIds } = useGameState(gameId);
const { deckCardsIds, placedCardsIds } = useGameState(gameId);

const { onCardEvent } = useCardEventHandler();
const { onCardEvent } = useCardEventHandler();

return (
<CardsGameListeners onEvent={onCardEvent} boardScale={scale} panAmount={pan} currentGameState={state}>
<div className="board-wrapper">
<TransformComponent>
<Board>
{placedCardsIds.map(id => (
<DraggableCard key={id} id={id} />
))}
</Board>
</TransformComponent>
<BottomWidgetsRow />
</div>
<DeckPanel cardsIds={deckCardsIds} />
</CardsGameListeners>
);
},
);
return (
<CardsGameListeners onEvent={onCardEvent} boardScale={scale} panAmount={pan} currentGameState={state}>
<div className="board-wrapper">
<TransformComponent>
<Board>
{placedCardsIds.map(id => (
<DraggableCard key={id} id={id} />
))}
</Board>
</TransformComponent>
<BottomWidgetsRow fitWindow={fitWindow} zoomIn={zoomIn} zoomOut={zoomOut} />
</div>
<DeckPanel cardsIds={deckCardsIds} />
</CardsGameListeners>
);
});

const wheelConfig = { step: 70 };
const transformOptions: React.ComponentProps<typeof TransformWrapper>['options'] = {
minScale: 0.5,
contentClass: 'zooming-panning-board',
} as any;

const GameWrapper = ({ gameId, transformProps }: { transformProps: TansformRenderProps; gameId: string }) => {
console.debug(transformProps);
const { scale, zoomIn, zoomOut, positionX, positionY, setScale } = transformProps;
const pan = useMemo(() => ({ x: positionX, y: positionY }), [positionX, positionY]);
const fitWindow = useCallback(() => {
setScale(0.5);
}, [setScale]);

return <Game pan={pan} scale={scale} gameId={gameId} zoomIn={zoomIn} zoomOut={zoomOut} fitWindow={fitWindow} />;
};

const GameView: React.FC = () => {
const params = useParams<{ gameId: string }>();

const gameId = params.gameId;

const renderGame = useCallback(
(transformProps: TansformRenderProps) => {
const scale = transformProps.scale;
const pan = { x: transformProps.positionX, y: transformProps.positionY };
return <Game pan={pan} scale={scale} gameId={gameId} />;
return <GameWrapper transformProps={transformProps} gameId={gameId} />;
},
[gameId],
);
Expand Down
3 changes: 3 additions & 0 deletions packages/game-app/src/gameView/types/tansformRenderProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,8 @@ export interface TansformRenderProps {
scalePadding: ScalePadding;
zoomIn: () => void;
zoomOut: () => void;
setScale: (scale: number) => void;
setPositionY: (y: number) => void;
setPositionX: (x: number) => void;
resetTransform: () => void;
}
40 changes: 0 additions & 40 deletions packages/game-app/src/temporary.css
Original file line number Diff line number Diff line change
Expand Up @@ -97,46 +97,6 @@ button:focus {
outline: none;
}

button:hover {
transform: scale(1.05);
}

button {
cursor: pointer;
}

button.primary {
background-color: #101820; /* Green */
border: none;
color: white;
padding: 10px 35px;
text-align: center;
text-decoration: none;
font-size: 16px;
border-radius: 25px;
font-weight: 600;
margin: 15px 0;
}

button.icon-button {
background-color: transparent;
border: none;
padding: 8px;
}

button.link {
background-color: transparent;
border: 0;
text-decoration: underline;
font-size: 15px;
color: #9f998f;
}

input + button.icon-button {
position: absolute;
right: 10px;
}

/** eye icon */
.gg-eye {
position: relative;
Expand Down

0 comments on commit c55c1e5

Please sign in to comment.