Skip to content

Commit

Permalink
Enable cards to be selected and the table to be reset
Browse files Browse the repository at this point in the history
  • Loading branch information
fatfisz committed May 5, 2020
1 parent 996c532 commit eb57786
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 13 deletions.
4 changes: 4 additions & 0 deletions package.json
Expand Up @@ -7,8 +7,12 @@
"license": "MIT",
"private": true,
"dependencies": {
"@types/classnames": "^2.2.10",
"@types/node": "^13.13.4",
"@types/react": "^16.9.34",
"classnames": "^2.2.6",
"fast-deep-equal": "^3.1.1",
"immer": "^6.0.4",
"next": "^9.3.6",
"prettier": "^2.0.5",
"react": "^16.13.1",
Expand Down
7 changes: 6 additions & 1 deletion pages/index.tsx
@@ -1,8 +1,13 @@
import { Table } from 'components/Table';
import { TableStateProvider } from 'components/TableStateContext';
import { MaybeCardDescription } from 'types/Card';

export default function Index() {
return <Table cards={cards} />;
return (
<TableStateProvider>
<Table cards={cards} />
</TableStateProvider>
);
}

const cards: MaybeCardDescription[] = [
Expand Down
29 changes: 20 additions & 9 deletions src/components/Card.tsx
@@ -1,6 +1,11 @@
import classNames from 'classnames';
import { useContext } from 'react';

import { shapeImageDescriptions } from 'components/CardDefs';
import { TableStateContext } from 'components/TableStateContext';
import { cardHeight, cardWidth } from 'config/card';
import { Color, Number, Shade, Shape } from 'types/Card';
import * as colors from 'config/colors';

export function Card({
color,
Expand All @@ -13,9 +18,16 @@ export function Card({
shade: Shade;
shape: Shape;
}) {
const card = { color, number, shade, shape };
const { checkIsSelected, select } = useContext(TableStateContext);
return (
<>
<div className="card">
<div
className={classNames('card', {
selected: checkIsSelected(card),
})}
onClick={() => select(card)}
>
{Array.from({ length: number }, (_value, key) => (
<ShapeImage key={key} color={color} shade={shade} shape={shape} />
))}
Expand All @@ -25,15 +37,20 @@ export function Card({
.card {
align-content: center;
background-color: white;
border: 1px solid #ccc;
border: 1px solid ${colors.lightGrey};
border-radius: 8px;
box-shadow: 0px 2px 6px 0px #666;
box-shadow: 0px 2px 6px 0px ${colors.darkGrey};
display: grid;
gap: 16px;
height: ${cardHeight}px;
justify-content: center;
width: ${cardWidth}px;
}
.card.selected {
border: 4px solid ${colors.blue};
box-shadow: 0px 4px 4px 2px ${colors.darkGrey};
}
`}</style>
</>
);
Expand Down Expand Up @@ -109,9 +126,3 @@ function getBackground(color: Color, shade: Shade) {
`;
}
}

const colors: Record<Color, string> = {
red: '#fd0484',
green: '#69db71',
purple: '#7f7cd4',
};
29 changes: 26 additions & 3 deletions src/components/Table.tsx
@@ -1,4 +1,7 @@
import { MouseEvent, useCallback, useContext } from 'react';

import { Card } from 'components/Card';
import { TableStateContext } from 'components/TableStateContext';
import { cardHeight, cardWidth, rowCount } from 'config/card';
import { MaybeCardDescription } from 'types/Card';

Expand All @@ -8,18 +11,21 @@ const gap = 32;
export function Table({ cards }: { cards: MaybeCardDescription[] }) {
const columnCount = Math.ceil(cards.length / rowCount);
const width = 2 * padding + columnCount * (cardWidth + gap) - gap;
const resetOnClick = useResetOnClick();
return (
<>
<div className="center-wrapper">
<div className="table" style={{ width }}>
<div className="center-wrapper" onClick={resetOnClick}>
<div className="table mouse-fallthrough" style={{ width }}>
{cards.map((cardProps, index) =>
typeof cardProps === 'undefined' ? (
<div
key={index}
style={{ height: cardHeight, width: cardWidth }}
/>
) : (
<Card key={index} {...cardProps} />
<div key={index} className="enable-pointer-events">
<Card {...cardProps} />
</div>
)
)}
</div>
Expand All @@ -39,9 +45,26 @@ export function Table({ cards }: { cards: MaybeCardDescription[] }) {
grid-template-rows: repeat(${rowCount}, max-content);
justify-content: start;
padding: ${padding}px;
pointer-events: none;
transition: width 400ms;
}
.enable-pointer-events {
pointer-events: auto;
}
`}</style>
</>
);
}

function useResetOnClick() {
const { reset } = useContext(TableStateContext);
return useCallback(
(event: MouseEvent) => {
if (event.currentTarget === event.target) {
reset();
}
},
[reset]
);
}
65 changes: 65 additions & 0 deletions src/components/TableStateContext.tsx
@@ -0,0 +1,65 @@
import equal from 'fast-deep-equal';
import produce, { Draft } from 'immer';
import { createContext, ReactNode, useCallback, useReducer } from 'react';

import { CardDescription } from 'types/Card';

interface TableState {
selected: Readonly<CardDescription[]>;
}

type TableStateAction =
| { type: 'select'; payload: CardDescription }
| { type: 'reset' };

export const TableStateContext = createContext<{
checkIsSelected(card: CardDescription): boolean;
select(card: CardDescription): void;
reset(): void;
}>({
checkIsSelected: () => false,
select: () => {},
reset: () => {},
});

const initialState: TableState = {
selected: [],
};

export function TableStateProvider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(tableStateReducer, initialState);
const value = {
checkIsSelected: useCallback(
(cardQuery: CardDescription) =>
state.selected.some((card) => equal(card, cardQuery)),
[state.selected]
),
select: useCallback(
(card: CardDescription) => dispatch({ type: 'select', payload: card }),
[]
),
reset: useCallback(() => dispatch({ type: 'reset' }), []),
};

return (
<TableStateContext.Provider value={value}>
{children}
</TableStateContext.Provider>
);
}

const tableStateReducer = produce(
(state: Draft<TableState>, action: TableStateAction) => {
switch (action.type) {
case 'select':
if (!state.selected.some((card) => equal(card, action.payload))) {
state.selected.push(action.payload);
}
return;

case 'reset':
state.selected = [];
return;
}
}
);
13 changes: 13 additions & 0 deletions src/config/colors.tsx
@@ -0,0 +1,13 @@
export const red = '#fd0484';

export const green = '#69db71';

export const purple = '#7f7cd4';

export const yellow = '#eddb13';

export const blue = '#2bb8e3';

export const lightGrey = '#ccc';

export const darkGrey = '#444';
15 changes: 15 additions & 0 deletions yarn.lock
Expand Up @@ -935,6 +935,11 @@
resolved "https://registry.yarnpkg.com/@scarf/scarf/-/scarf-1.0.4.tgz#d7c09e7d38428123df18a8d83a4bb5d09517d952"
integrity sha512-lkhjzeYyYAG4VvdrjvbZCOYzXH5vCwfzYj9xJ4zHDgyYIOzObZwcsbW6W1q5Z4tywrb14oG/tfsFAMMQPCTFqw==

"@types/classnames@^2.2.10":
version "2.2.10"
resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.10.tgz#cc658ca319b6355399efc1f5b9e818f1a24bf999"
integrity sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ==

"@types/node@^13.13.4":
version "13.13.4"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.4.tgz#1581d6c16e3d4803eb079c87d4ac893ee7501c2c"
Expand Down Expand Up @@ -1659,6 +1664,11 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"

classnames@^2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==

clone-deep@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
Expand Down Expand Up @@ -2722,6 +2732,11 @@ iferr@^0.1.5:
resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE=

immer@^6.0.4:
version "6.0.4"
resolved "https://registry.yarnpkg.com/immer/-/immer-6.0.4.tgz#f3a3de1b8db24012fb842ad25e33fbf6c280c481"
integrity sha512-MKC+V5Zf/tdPlp1Uw4Iu4zHD1mzjyMUkc+KC5/k7cWWEYsz+a11ayUo86YukWfiG4fDXhEaKxjpbOIL8l4uu5A==

import-fresh@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"
Expand Down

0 comments on commit eb57786

Please sign in to comment.