From 75bee681f95a5c580a3385d9de6ddcbed39682e3 Mon Sep 17 00:00:00 2001 From: Matt Layton Date: Sat, 3 Sep 2022 14:36:27 +0100 Subject: [PATCH 01/28] feat: add basic components and types --- src/App.css | 38 ------ src/App.module.css | 20 ++++ src/App.test.tsx | 11 +- src/App.tsx | 32 ++--- src/components/Board/Board.module.css | 8 ++ src/components/Board/Board.tsx | 33 +++++ src/components/Board/index.ts | 1 + src/components/Card/Card.module.css | 5 + src/components/Card/Card.tsx | 17 +++ src/components/Card/index.ts | 2 + src/components/Column/Column.module.css | 14 +++ src/components/Column/Column.tsx | 35 ++++++ src/components/Column/index.ts | 1 + .../VisuallyHidden/VisuallyHidden.module.css | 9 ++ .../VisuallyHidden/VisuallyHidden.tsx | 17 +++ src/components/VisuallyHidden/index.ts | 1 + src/index.css | 113 ++++++++++++++++-- src/index.tsx | 5 +- src/logo.svg | 1 - src/types/Card.ts | 6 + src/types/Column.ts | 5 + 21 files changed, 299 insertions(+), 75 deletions(-) delete mode 100644 src/App.css create mode 100644 src/App.module.css create mode 100644 src/components/Board/Board.module.css create mode 100644 src/components/Board/Board.tsx create mode 100644 src/components/Board/index.ts create mode 100644 src/components/Card/Card.module.css create mode 100644 src/components/Card/Card.tsx create mode 100644 src/components/Card/index.ts create mode 100644 src/components/Column/Column.module.css create mode 100644 src/components/Column/Column.tsx create mode 100644 src/components/Column/index.ts create mode 100644 src/components/VisuallyHidden/VisuallyHidden.module.css create mode 100644 src/components/VisuallyHidden/VisuallyHidden.tsx create mode 100644 src/components/VisuallyHidden/index.ts delete mode 100644 src/logo.svg create mode 100644 src/types/Card.ts create mode 100644 src/types/Column.ts diff --git a/src/App.css b/src/App.css deleted file mode 100644 index 74b5e05..0000000 --- a/src/App.css +++ /dev/null @@ -1,38 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/src/App.module.css b/src/App.module.css new file mode 100644 index 0000000..79e5369 --- /dev/null +++ b/src/App.module.css @@ -0,0 +1,20 @@ +:root { + /* spacing scale */ + --space-1: 0.625rem; + --space-2: 0.75rem; + --space-3: 0.875rem; + --space-4: 1rem; + --space-5: 1.125rem; + + /* generic colors */ + --c-white: #fff; + --c-grey: #ebecf0; + + /* component colors */ + --c-card: var(--c-white); + --c-column: var(--c-grey); +} + +body { + font-family: Arial, Helvetica, sans-serif; +} \ No newline at end of file diff --git a/src/App.test.tsx b/src/App.test.tsx index 2a68616..f8107b7 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -1,9 +1,6 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; -import App from './App'; +// import { render, screen } from '@testing-library/react'; -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); +// import App from './App'; + +test.todo('renders app'); diff --git a/src/App.tsx b/src/App.tsx index a53698a..51a9745 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,25 +1,19 @@ import React from 'react'; -import logo from './logo.svg'; -import './App.css'; -function App() { +import Board from './components/Board'; + +import { Card } from './types/Card'; + +import './App.module.css'; + +const cards: Card[] = [ + { id: '0', title: 'Example 1', weight: 0 }, + { id: '1', title: 'Example 2', weight: 1 }, +]; + +const App: React.FunctionComponent = () => { return ( -
-
- logo -

- Edit src/App.tsx and save to reload. -

- - Learn React - -
-
+ ); } diff --git a/src/components/Board/Board.module.css b/src/components/Board/Board.module.css new file mode 100644 index 0000000..130d788 --- /dev/null +++ b/src/components/Board/Board.module.css @@ -0,0 +1,8 @@ +.root { + display: flex; + padding: var(--space-1); +} + +.root > * + * { + margin-left: var(--space-2); +} diff --git a/src/components/Board/Board.tsx b/src/components/Board/Board.tsx new file mode 100644 index 0000000..b2195c7 --- /dev/null +++ b/src/components/Board/Board.tsx @@ -0,0 +1,33 @@ +import VisuallyHidden from '../VisuallyHidden'; +import Card from '../Card'; +import Column from '../Column'; + +import { Card as ICard } from '../../types/Card'; + +import styles from './Board.module.css'; + +interface Props { + cards?: ICard[]; +} + +const Board: React.FunctionComponent = ({ cards = [] }) => ( + <> + Trello Board + +
+ + {cards.map(card => ( + + ))} + + + + {cards.map(card => ( + + ))} + +
+ +); + +export default Board; diff --git a/src/components/Board/index.ts b/src/components/Board/index.ts new file mode 100644 index 0000000..f296d97 --- /dev/null +++ b/src/components/Board/index.ts @@ -0,0 +1 @@ +export { default } from './Board'; \ No newline at end of file diff --git a/src/components/Card/Card.module.css b/src/components/Card/Card.module.css new file mode 100644 index 0000000..2daccdd --- /dev/null +++ b/src/components/Card/Card.module.css @@ -0,0 +1,5 @@ +.root { + padding: var(--space-1); + border-radius: 3px; + background-color: var(--c-card); +} diff --git a/src/components/Card/Card.tsx b/src/components/Card/Card.tsx new file mode 100644 index 0000000..2a7b7ec --- /dev/null +++ b/src/components/Card/Card.tsx @@ -0,0 +1,17 @@ +import { Card as ICard } from '../../types/Card'; + +import styles from './Card.module.css'; + +export type Props = ICard & { + children?: React.ReactNode; +}; + +const Card: React.FunctionComponent = ({ title, children }) => ( +
+

{title}

+ + {children} +
+); + +export default Card; diff --git a/src/components/Card/index.ts b/src/components/Card/index.ts new file mode 100644 index 0000000..83385cb --- /dev/null +++ b/src/components/Card/index.ts @@ -0,0 +1,2 @@ +export { default } from './Card'; +export * from './Card'; diff --git a/src/components/Column/Column.module.css b/src/components/Column/Column.module.css new file mode 100644 index 0000000..7dcf493 --- /dev/null +++ b/src/components/Column/Column.module.css @@ -0,0 +1,14 @@ +.root { + width: 270px; + padding: var(--space-2); + border-radius: 3px; + background-color: var(--c-column); +} + +.card + .card { + margin-top: var(--space-1); +} + +.title { + margin-bottom: var(--space-2); +} \ No newline at end of file diff --git a/src/components/Column/Column.tsx b/src/components/Column/Column.tsx new file mode 100644 index 0000000..7a5fcd9 --- /dev/null +++ b/src/components/Column/Column.tsx @@ -0,0 +1,35 @@ +import React from 'react'; + +import { Column as IColumn } from '../../types/Column'; +import { Props as CardProps } from '../Card'; + +import styles from './Column.module.css'; + +type Props = IColumn & { + children?: React.ReactElement | Array>; +}; + +const Column: React.FunctionComponent = ({ title, children }) => { + const hasCards = Boolean(React.Children.count(children)); + + return ( +
+

{title}

+ + {hasCards && ( +
    + {React.Children.map(children, child => ( +
  • + {child} +
  • + ))} +
+ )} +
+ ); +}; + +export default Column; \ No newline at end of file diff --git a/src/components/Column/index.ts b/src/components/Column/index.ts new file mode 100644 index 0000000..ee946cc --- /dev/null +++ b/src/components/Column/index.ts @@ -0,0 +1 @@ +export { default } from './Column'; \ No newline at end of file diff --git a/src/components/VisuallyHidden/VisuallyHidden.module.css b/src/components/VisuallyHidden/VisuallyHidden.module.css new file mode 100644 index 0000000..6ae267a --- /dev/null +++ b/src/components/VisuallyHidden/VisuallyHidden.module.css @@ -0,0 +1,9 @@ +.root { + position: absolute; + clip: rect(1px, 1px, 1px, 1px); + padding: 0; + border: 0; + height: 1px; + width: 1px; + overflow: hidden; +} \ No newline at end of file diff --git a/src/components/VisuallyHidden/VisuallyHidden.tsx b/src/components/VisuallyHidden/VisuallyHidden.tsx new file mode 100644 index 0000000..e643a3d --- /dev/null +++ b/src/components/VisuallyHidden/VisuallyHidden.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; + +import styles from './VisuallyHidden.module.css'; + +export interface VisuallyHiddenProps { + as?: T; + children?: React.ReactNode; +} + +// https://itnext.io/react-polymorphic-components-with-typescript-f7ce72ea7af2 +const VisuallyHidden = ({ as, ...props }: VisuallyHiddenProps & Omit, keyof VisuallyHiddenProps>) => { + const Component = as || 'div'; + + return ; +}; + +export default VisuallyHidden; diff --git a/src/components/VisuallyHidden/index.ts b/src/components/VisuallyHidden/index.ts new file mode 100644 index 0000000..a2f97cc --- /dev/null +++ b/src/components/VisuallyHidden/index.ts @@ -0,0 +1 @@ +export { default } from './VisuallyHidden'; \ No newline at end of file diff --git a/src/index.css b/src/index.css index ec2585e..e60bbe8 100644 --- a/src/index.css +++ b/src/index.css @@ -1,13 +1,108 @@ -body { +*, +*::before, +*::after { + box-sizing: border-box; +} + +*:focus { + outline: 1px dotted currentColor; + outline-offset: 0.5rem; +} + +/* Remove default padding */ +ul, +ol { + padding: 0; +} + +/* Remove default margin */ +body, +h1, +h2, +h3, +h4, +p, +ul, +ol, +li, +figure, +figcaption, +blockquote, +dl, +dd { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; } -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; +:root { + min-height: 100%; } + +/* Set core body defaults */ +body { + min-height: 100%; + scroll-behavior: smooth; + text-rendering: optimizeSpeed; + line-height: 1.5; +} + +/* Remove list styles on ul, ol elements */ +ul, +ol { + list-style: none; +} + +/* Have link and buttons be indistinguishable */ +a, +button { + cursor: pointer; +} + +a { + text-decoration: none; +} + +button { + border: none; + background: none; +} + +/* Make images easier to work with */ +img { + max-width: 100%; + display: block; +} + +/* Inherit fonts for inputs and buttons */ +input, +button, +textarea, +select { + font: inherit; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font: inherit; +} + +[hidden] { + display: none; +} + +[inert] { + opacity: 0.25; +} + +/* Remove all animations and transitions for people that prefer not to see them */ +@media (prefers-reduced-motion: reduce) { + * { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} \ No newline at end of file diff --git a/src/index.tsx b/src/index.tsx index 032464f..2825f49 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,12 +1,15 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import './index.css'; + import App from './App'; import reportWebVitals from './reportWebVitals'; +import './index.css'; + const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement ); + root.render( diff --git a/src/logo.svg b/src/logo.svg deleted file mode 100644 index 9dfc1c0..0000000 --- a/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/types/Card.ts b/src/types/Card.ts new file mode 100644 index 0000000..1cfc18d --- /dev/null +++ b/src/types/Card.ts @@ -0,0 +1,6 @@ +export interface Card { + id: string; + title: string; + description?: string; + weight: number; +} diff --git a/src/types/Column.ts b/src/types/Column.ts new file mode 100644 index 0000000..bd6a965 --- /dev/null +++ b/src/types/Column.ts @@ -0,0 +1,5 @@ +export interface Column { + id: string; + title: string; + weight: number; +} From 16474a24841bfa93c7338f5ff5a7837f0284f953 Mon Sep 17 00:00:00 2001 From: Matt Layton Date: Sat, 3 Sep 2022 14:59:38 +0100 Subject: [PATCH 02/28] feat: add localstorage support for global state --- src/App.tsx | 39 ++++++++++++++++++++++----- src/components/Board/Board.module.css | 4 +-- src/components/Board/Board.tsx | 30 ++++++++++----------- src/components/Column/Column.tsx | 2 +- src/hooks/useLocalStorage.ts | 36 +++++++++++++++++++++++++ src/types/Column.ts | 3 +++ src/types/GlobalState.ts | 5 ++++ 7 files changed, 94 insertions(+), 25 deletions(-) create mode 100644 src/hooks/useLocalStorage.ts create mode 100644 src/types/GlobalState.ts diff --git a/src/App.tsx b/src/App.tsx index 51a9745..57f6578 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,19 +1,44 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import Board from './components/Board'; -import { Card } from './types/Card'; +import { GlobalState } from './types/GlobalState'; import './App.module.css'; +import useLocalStorage from './hooks/useLocalStorage'; -const cards: Card[] = [ - { id: '0', title: 'Example 1', weight: 0 }, - { id: '1', title: 'Example 2', weight: 1 }, -]; +const defaultState = { + columns: [ + { + id: '0', + title: 'Column 1', + weight: 0, + cards: [ + { id: '0', title: 'Example 1', weight: 0 }, + { id: '1', title: 'Example 2', weight: 1 }, + ], + }, + { + id: '1', + title: 'Column 2', + weight: 1, + cards: [ + { id: '0', title: 'Example 1', weight: 0 }, + ], + }, + ], +}; const App: React.FunctionComponent = () => { + const [state, setState] = useLocalStorage('trello', defaultState); + + // TODO: temporary, remove + useEffect(() => { + setState(state); + }, [state, setState]); + return ( - + ); } diff --git a/src/components/Board/Board.module.css b/src/components/Board/Board.module.css index 130d788..ea41e40 100644 --- a/src/components/Board/Board.module.css +++ b/src/components/Board/Board.module.css @@ -1,8 +1,8 @@ -.root { +.columns { display: flex; padding: var(--space-1); } -.root > * + * { +.columns > * + * { margin-left: var(--space-2); } diff --git a/src/components/Board/Board.tsx b/src/components/Board/Board.tsx index b2195c7..8207224 100644 --- a/src/components/Board/Board.tsx +++ b/src/components/Board/Board.tsx @@ -2,31 +2,31 @@ import VisuallyHidden from '../VisuallyHidden'; import Card from '../Card'; import Column from '../Column'; -import { Card as ICard } from '../../types/Card'; +import { Column as IColumn } from '../../types/Column'; import styles from './Board.module.css'; interface Props { - cards?: ICard[]; + columns?: IColumn[]; } -const Board: React.FunctionComponent = ({ cards = [] }) => ( +const Board: React.FunctionComponent = ({ columns = [] }) => ( <> Trello Board -
- - {cards.map(card => ( - + {Boolean(columns.length) && ( +
    + {columns.map(column => ( +
  • + + {column.cards.map(card => ( + + ))} + +
  • ))} - - - - {cards.map(card => ( - - ))} - -
+ + )} ); diff --git a/src/components/Column/Column.tsx b/src/components/Column/Column.tsx index 7a5fcd9..9797b5a 100644 --- a/src/components/Column/Column.tsx +++ b/src/components/Column/Column.tsx @@ -5,7 +5,7 @@ import { Props as CardProps } from '../Card'; import styles from './Column.module.css'; -type Props = IColumn & { +type Props = Pick & { children?: React.ReactElement | Array>; }; diff --git a/src/hooks/useLocalStorage.ts b/src/hooks/useLocalStorage.ts new file mode 100644 index 0000000..18befb5 --- /dev/null +++ b/src/hooks/useLocalStorage.ts @@ -0,0 +1,36 @@ +import { useState } from 'react'; + +const useLocalStorage = (key: string, initialValue: T) => { + const [storedValue, setStoredValue] = useState(() => { + if (typeof window === 'undefined') { + return initialValue; + } + + try { + const item = window.localStorage.getItem(key); + return item ? JSON.parse(item) : initialValue; + } catch (error) { + console.log(error); + + return initialValue; + } + }); + + const setValue = (value: T | ((val: T) => T)) => { + try { + const valueToStore = value instanceof Function ? value(storedValue) : value; + + setStoredValue(valueToStore); + + if (typeof window !== 'undefined') { + window.localStorage.setItem(key, JSON.stringify(valueToStore)); + } + } catch (error) { + console.log(error); + } + }; + + return [storedValue, setValue] as const; +}; + +export default useLocalStorage; diff --git a/src/types/Column.ts b/src/types/Column.ts index bd6a965..cd19707 100644 --- a/src/types/Column.ts +++ b/src/types/Column.ts @@ -1,5 +1,8 @@ +import { Card } from './Card'; + export interface Column { id: string; title: string; weight: number; + cards: Card[]; } diff --git a/src/types/GlobalState.ts b/src/types/GlobalState.ts new file mode 100644 index 0000000..4d8ae7e --- /dev/null +++ b/src/types/GlobalState.ts @@ -0,0 +1,5 @@ +import { Column } from './Column'; + +export interface GlobalState { + columns: Column[]; +} From 89c44094b470123f2f5b2345e3f0de9be8f3c611 Mon Sep 17 00:00:00 2001 From: Matt Layton Date: Sat, 3 Sep 2022 16:18:20 +0100 Subject: [PATCH 03/28] improve state management and allow for column titles to be modified --- src/App.tsx | 45 +++----------- src/AppContext.tsx | 81 +++++++++++++++++++++++++ src/components/Board/Board.tsx | 49 ++++++++------- src/components/Column/Column.module.css | 3 + src/components/Column/Column.tsx | 24 ++++++-- src/types/Column.ts | 2 +- src/types/GlobalState.ts | 13 +++- 7 files changed, 149 insertions(+), 68 deletions(-) create mode 100644 src/AppContext.tsx diff --git a/src/App.tsx b/src/App.tsx index 57f6578..cc3de8b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,45 +1,14 @@ -import React, { useEffect } from 'react'; +import React from 'react'; +import { AppProvider } from './AppContext'; import Board from './components/Board'; -import { GlobalState } from './types/GlobalState'; - import './App.module.css'; -import useLocalStorage from './hooks/useLocalStorage'; - -const defaultState = { - columns: [ - { - id: '0', - title: 'Column 1', - weight: 0, - cards: [ - { id: '0', title: 'Example 1', weight: 0 }, - { id: '1', title: 'Example 2', weight: 1 }, - ], - }, - { - id: '1', - title: 'Column 2', - weight: 1, - cards: [ - { id: '0', title: 'Example 1', weight: 0 }, - ], - }, - ], -}; - -const App: React.FunctionComponent = () => { - const [state, setState] = useLocalStorage('trello', defaultState); - - // TODO: temporary, remove - useEffect(() => { - setState(state); - }, [state, setState]); - return ( - - ); -} +const App: React.FunctionComponent = () => ( + + + +); export default App; diff --git a/src/AppContext.tsx b/src/AppContext.tsx new file mode 100644 index 0000000..c6dfb9a --- /dev/null +++ b/src/AppContext.tsx @@ -0,0 +1,81 @@ +import React, { createContext } from 'react'; + +import { GlobalState } from './types/GlobalState'; +import { Column } from './types/Column'; + +import useLocalStorage from './hooks/useLocalStorage'; + +interface GlobalAppContext { + state: GlobalState; + updateColumn: (columnId: string, value: Partial) => void; +} + +const defaultState = { + columnsById: { + '0': { + id: '0', + title: 'Column 1', + weight: 0, + cardsById: { + '0': { id: '0', title: 'Example 1', weight: 0 }, + '1': { id: '1', title: 'Example 2', weight: 1 }, + }, + }, + '1': { + id: '1', + title: 'Column 2', + weight: 1, + cardsById: { + '0': { id: '0', title: 'Example 1', weight: 0 }, + }, + }, + }, +}; + +const AppContext = createContext(null); + +interface Props { + children?: React.ReactNode; +} + +const AppProvider: React.FunctionComponent = props => { + const [state, setState] = useLocalStorage('trello', defaultState); + + /** + * Allows for partial column updates + * + * @param columnId The ID of the column + * @param value The values to override the column with + */ + const updateColumn = (columnId: string, value: Partial): void => { + if (!state.columnsById[columnId]) { + console.warn('cannot update missing column', columnId); + } + + setState({ + ...state, + columnsById: { + ...state.columnsById, + [columnId]: { + ...state.columnsById[columnId], + ...value, + }, + }, + }); + }; + + return ( + + ) +}; + +export { + AppContext, + AppProvider, +}; diff --git a/src/components/Board/Board.tsx b/src/components/Board/Board.tsx index 8207224..9cb9da7 100644 --- a/src/components/Board/Board.tsx +++ b/src/components/Board/Board.tsx @@ -1,33 +1,36 @@ +import { useContext } from 'react'; + +import { AppContext } from '../../AppContext'; import VisuallyHidden from '../VisuallyHidden'; import Card from '../Card'; import Column from '../Column'; -import { Column as IColumn } from '../../types/Column'; - import styles from './Board.module.css'; -interface Props { - columns?: IColumn[]; -} +const Board: React.FunctionComponent = () => { + const ctx = useContext(AppContext); -const Board: React.FunctionComponent = ({ columns = [] }) => ( - <> - Trello Board + const columns = Object.values(ctx?.state.columnsById || {}); + + return ( + <> + Trello Board - {Boolean(columns.length) && ( -
    - {columns.map(column => ( -
  • - - {column.cards.map(card => ( - - ))} - -
  • - ))} -
- )} - -); + {Boolean(columns.length) && ( +
    + {columns.map(column => ( +
  • + + {Object.values(column.cardsById).map(card => ( + + ))} + +
  • + ))} +
+ )} + + ); +}; export default Board; diff --git a/src/components/Column/Column.module.css b/src/components/Column/Column.module.css index 7dcf493..d5e801e 100644 --- a/src/components/Column/Column.module.css +++ b/src/components/Column/Column.module.css @@ -11,4 +11,7 @@ .title { margin-bottom: var(--space-2); + border: none; + background: none; + cursor: pointer; } \ No newline at end of file diff --git a/src/components/Column/Column.tsx b/src/components/Column/Column.tsx index 9797b5a..ed29b5c 100644 --- a/src/components/Column/Column.tsx +++ b/src/components/Column/Column.tsx @@ -1,7 +1,10 @@ -import React from 'react'; +import React, { useContext } from 'react'; -import { Column as IColumn } from '../../types/Column'; +import { AppContext } from '../../AppContext'; import { Props as CardProps } from '../Card'; +import VisuallyHidden from '../VisuallyHidden'; + +import { Column as IColumn } from '../../types/Column'; import styles from './Column.module.css'; @@ -9,12 +12,25 @@ type Props = Pick & { children?: React.ReactElement | Array>; }; -const Column: React.FunctionComponent = ({ title, children }) => { +const Column: React.FunctionComponent = ({ id, title, children }) => { + const ctx = useContext(AppContext); + const hasCards = Boolean(React.Children.count(children)); return (
-

{title}

+ {title} + + { + ctx?.updateColumn(id, { + title: event.target.value, + }) + }} + className={styles.title} + /> {hasCards && (
    diff --git a/src/types/Column.ts b/src/types/Column.ts index cd19707..981ef16 100644 --- a/src/types/Column.ts +++ b/src/types/Column.ts @@ -4,5 +4,5 @@ export interface Column { id: string; title: string; weight: number; - cards: Card[]; + cardsById: { [key: string]: Card }; } diff --git a/src/types/GlobalState.ts b/src/types/GlobalState.ts index 4d8ae7e..889dd61 100644 --- a/src/types/GlobalState.ts +++ b/src/types/GlobalState.ts @@ -1,5 +1,14 @@ -import { Column } from './Column'; +import { Column as IColumn } from './Column'; +import { Card as ICard } from './Card'; + +type CardsById = { [key: string]: ICard }; + +interface Column extends Omit { + cardsById: CardsById; +} + +type ColumnsById = { [key: string]: Column }; export interface GlobalState { - columns: Column[]; + columnsById: ColumnsById; } From 25baa8706df0ed76903ebdf2c1efb480258b9752 Mon Sep 17 00:00:00 2001 From: Matt Layton Date: Sat, 3 Sep 2022 19:00:22 +0100 Subject: [PATCH 04/28] feat: allow cards to be added --- package.json | 4 + src/App.module.css | 18 +- src/App.tsx | 2 + src/AppContext.tsx | 19 + src/components/Button/Button.module.css | 10 + src/components/Button/Button.tsx | 15 + src/components/Button/index.ts | 1 + src/components/Card/Card.module.css | 2 +- src/components/Card/Card.tsx | 41 +- src/components/Column/Column.module.css | 15 +- src/components/Column/Column.tsx | 105 +- src/components/Modal/Modal.module.css | 21 + src/components/Modal/Modal.tsx | 43 + src/components/Modal/index.ts | 1 + src/hooks/useOnClickOutside.ts | 26 + src/index.css | 9 +- yarn.lock | 9055 +++++++++++++++++++++++ 17 files changed, 9342 insertions(+), 45 deletions(-) create mode 100644 src/components/Button/Button.module.css create mode 100644 src/components/Button/Button.tsx create mode 100644 src/components/Button/index.ts create mode 100644 src/components/Modal/Modal.module.css create mode 100644 src/components/Modal/Modal.tsx create mode 100644 src/components/Modal/index.ts create mode 100644 src/hooks/useOnClickOutside.ts create mode 100644 yarn.lock diff --git a/package.json b/package.json index 1b969c6..e204a33 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "react-dom": "^18.2.0", "react-scripts": "5.0.1", "typescript": "^4.8.2", + "uuid": "8.3.2", "web-vitals": "^2.1.4" }, "scripts": { @@ -39,5 +40,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "@types/uuid": "8.3.4" } } diff --git a/src/App.module.css b/src/App.module.css index 79e5369..b4e5bb1 100644 --- a/src/App.module.css +++ b/src/App.module.css @@ -1,20 +1,26 @@ :root { /* spacing scale */ - --space-1: 0.625rem; - --space-2: 0.75rem; - --space-3: 0.875rem; - --space-4: 1rem; - --space-5: 1.125rem; + --space-1: 0.375rem; + --space-2: 0.5rem; + --space-3: 0.625rem; + --space-4: 0.75rem; + --space-5: 0.875rem; + --space-6: 1rem; + --space-7: 1.125rem; /* generic colors */ --c-white: #fff; --c-grey: #ebecf0; + --c-blue: #0079bf; /* component colors */ + --c-overlay: #000000a3; --c-card: var(--c-white); --c-column: var(--c-grey); + --c-button-primary: var(--c-blue); + --c-button-primary-text: var(--c-white); } body { font-family: Arial, Helvetica, sans-serif; -} \ No newline at end of file +} diff --git a/src/App.tsx b/src/App.tsx index cc3de8b..9b334a0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,8 @@ import './App.module.css'; const App: React.FunctionComponent = () => ( + +