aboutsummaryrefslogtreecommitdiffstats
path: root/public/projects/react-small-apps/apps/notebook/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'public/projects/react-small-apps/apps/notebook/src/components')
-rw-r--r--public/projects/react-small-apps/apps/notebook/src/components/commons/Button/Button.css78
-rw-r--r--public/projects/react-small-apps/apps/notebook/src/components/commons/Button/Button.js26
-rw-r--r--public/projects/react-small-apps/apps/notebook/src/components/commons/FormElements/Input/Input.js31
-rw-r--r--public/projects/react-small-apps/apps/notebook/src/components/commons/FormElements/TextArea/TextArea.js52
-rw-r--r--public/projects/react-small-apps/apps/notebook/src/components/commons/FormElements/index.js4
-rw-r--r--public/projects/react-small-apps/apps/notebook/src/components/commons/List/List.css3
-rw-r--r--public/projects/react-small-apps/apps/notebook/src/components/commons/List/List.js25
-rw-r--r--public/projects/react-small-apps/apps/notebook/src/components/commons/index.js5
-rw-r--r--public/projects/react-small-apps/apps/notebook/src/components/helpers/hooks/useToggle.js10
-rw-r--r--public/projects/react-small-apps/apps/notebook/src/components/layout/Footer/Footer.css8
-rw-r--r--public/projects/react-small-apps/apps/notebook/src/components/layout/Footer/Footer.js11
-rw-r--r--public/projects/react-small-apps/apps/notebook/src/components/layout/Header/Header.css7
-rw-r--r--public/projects/react-small-apps/apps/notebook/src/components/layout/Header/Header.js12
-rw-r--r--public/projects/react-small-apps/apps/notebook/src/components/layout/Main/Main.css3
-rw-r--r--public/projects/react-small-apps/apps/notebook/src/components/layout/Main/Main.js7
-rw-r--r--public/projects/react-small-apps/apps/notebook/src/components/layout/Nav/Nav.css65
-rw-r--r--public/projects/react-small-apps/apps/notebook/src/components/layout/Nav/Nav.js62
-rw-r--r--public/projects/react-small-apps/apps/notebook/src/components/layout/Nav/NavJump.js28
-rw-r--r--public/projects/react-small-apps/apps/notebook/src/components/layout/Page/Cover.css30
-rw-r--r--public/projects/react-small-apps/apps/notebook/src/components/layout/Page/Page.css83
-rw-r--r--public/projects/react-small-apps/apps/notebook/src/components/layout/Page/Page.js107
-rw-r--r--public/projects/react-small-apps/apps/notebook/src/components/layout/Page/PageToolbar.js27
-rw-r--r--public/projects/react-small-apps/apps/notebook/src/components/layout/index.js7
23 files changed, 691 insertions, 0 deletions
diff --git a/public/projects/react-small-apps/apps/notebook/src/components/commons/Button/Button.css b/public/projects/react-small-apps/apps/notebook/src/components/commons/Button/Button.css
new file mode 100644
index 0000000..16268d3
--- /dev/null
+++ b/public/projects/react-small-apps/apps/notebook/src/components/commons/Button/Button.css
@@ -0,0 +1,78 @@
+.btn {
+ cursor: pointer;
+}
+
+.btn--delete {
+ background: hsl(0, 44%, 44%);
+ border: none;
+ border-radius: 50%;
+ box-shadow: 0 0 0 2px hsl(0, 44%, 29%), 1px 2px 1px 2px hsl(0, 44%, 29%);
+ width: 2.4rem;
+ height: 2.4rem;
+ padding: 0.5rem;
+ transition: transform 0.3s ease-in-out 0s;
+}
+
+.btn--delete:hover,
+.btn--delete:focus {
+ background: hsl(0, 44%, 50%);
+ transform: scale(1.1);
+}
+
+.btn--delete:active {
+ background: hsl(0, 44%, 40%);
+ transform: scale(1);
+}
+
+.btn .icon {
+ height: 100%;
+ width: 100%;
+}
+
+.btn--delete #trash-lid-handle {
+ stroke: #fff;
+ stroke-width: 4;
+}
+
+.btn--delete #trash-container,
+.btn--delete #trash-lid {
+ fill: hsl(0, 44%, 49%);
+ stroke: #fff;
+ stroke-width: 5;
+}
+
+.btn--delete #trash-stroke1,
+.btn--delete #trash-stroke2,
+.btn--delete #trash-stroke3 {
+ fill: #fff;
+ stroke: #fff;
+ stroke-width: 1;
+}
+
+.btn--restore {
+ background: hsl(212, 44%, 44%);
+ border: none;
+ border-radius: 50%;
+ box-shadow: 0 0 0 2px hsl(212, 44%, 29%), 1px 2px 1px 2px hsl(212, 44%, 29%);
+ width: 2.4rem;
+ height: 2.4rem;
+ padding: 0.5rem;
+ transition: transform 0.3s ease-in-out 0s;
+}
+
+.btn--restore:hover {
+ background: hsl(212, 44%, 50%);
+ transform: scale(1.1);
+}
+
+.btn--restore:active {
+ background: hsl(212, 44%, 40%);
+ transform: scale(1);
+}
+
+.btn--restore #restore-circle,
+.btn--restore #restore-arrow,
+.btn--restore #restore-first-clock-hand,
+.btn--restore #restore-second-clock-hand {
+ fill: #fff;
+}
diff --git a/public/projects/react-small-apps/apps/notebook/src/components/commons/Button/Button.js b/public/projects/react-small-apps/apps/notebook/src/components/commons/Button/Button.js
new file mode 100644
index 0000000..4580815
--- /dev/null
+++ b/public/projects/react-small-apps/apps/notebook/src/components/commons/Button/Button.js
@@ -0,0 +1,26 @@
+import "./Button.css";
+
+function Button({
+ children,
+ onClickHandler,
+ onBlurHandler,
+ modifier,
+ additionalClassnames,
+}) {
+ let classNames = modifier ? `btn btn--${modifier}` : "btn";
+ classNames = additionalClassnames
+ ? `${classNames} ${additionalClassnames}`
+ : classNames;
+
+ return (
+ <button
+ className={classNames}
+ onClick={onClickHandler}
+ onBlur={onBlurHandler}
+ >
+ {children}
+ </button>
+ );
+}
+
+export default Button;
diff --git a/public/projects/react-small-apps/apps/notebook/src/components/commons/FormElements/Input/Input.js b/public/projects/react-small-apps/apps/notebook/src/components/commons/FormElements/Input/Input.js
new file mode 100644
index 0000000..7d8cb45
--- /dev/null
+++ b/public/projects/react-small-apps/apps/notebook/src/components/commons/FormElements/Input/Input.js
@@ -0,0 +1,31 @@
+import { forwardRef } from "react";
+
+function Input(
+ {
+ type = "text",
+ name,
+ value,
+ onChangeHandler,
+ onBlurHandler,
+ additionalClasses,
+ },
+ ref
+) {
+ const classNames = additionalClasses
+ ? `form__input ${additionalClasses}`
+ : "form__input";
+
+ return (
+ <input
+ ref={ref}
+ type={type}
+ name={name}
+ className={classNames}
+ value={value}
+ onChange={onChangeHandler}
+ onBlur={onBlurHandler}
+ />
+ );
+}
+
+export default forwardRef(Input);
diff --git a/public/projects/react-small-apps/apps/notebook/src/components/commons/FormElements/TextArea/TextArea.js b/public/projects/react-small-apps/apps/notebook/src/components/commons/FormElements/TextArea/TextArea.js
new file mode 100644
index 0000000..ca2a52e
--- /dev/null
+++ b/public/projects/react-small-apps/apps/notebook/src/components/commons/FormElements/TextArea/TextArea.js
@@ -0,0 +1,52 @@
+import { forwardRef, useEffect, useState } from "react";
+
+function autoGrow(field, initialValue = null) {
+ let fieldHeight = initialValue ?? field.style.height;
+ if (field.scrollHeight > field.clientHeight) {
+ fieldHeight = field.scrollHeight + "px";
+ }
+ return fieldHeight;
+}
+
+function isSetHeightNeeded(e) {
+ const key = e.key;
+ const isBackspace = key === "Backspace";
+ const isDelete = key === "Delete";
+ const isCtrlZ = e.ctrlKey && e.key === "z";
+ const isCut = e.ctrlKey && e.key === "x";
+ return isBackspace || isDelete || isCtrlZ || isCut;
+}
+
+function TextArea(
+ { value, name, onBlurHandler, onChangeHandler, additionalClasses },
+ ref
+) {
+ const [fieldHeight, setFieldHeight] = useState();
+ const classNames = additionalClasses
+ ? `form__textarea ${additionalClasses}`
+ : "form__textarea";
+
+ useEffect(() => {
+ ref && setFieldHeight(autoGrow(ref.current));
+ }, [ref]);
+
+ return (
+ <textarea
+ ref={ref}
+ className={classNames}
+ name={name}
+ value={value}
+ onChange={(e) => {
+ onChangeHandler(e);
+ setFieldHeight(autoGrow(e.target));
+ }}
+ onKeyDown={(e) => {
+ if (isSetHeightNeeded(e)) setFieldHeight(autoGrow(e.target, "auto"));
+ }}
+ onBlur={onBlurHandler}
+ style={{ height: fieldHeight }}
+ />
+ );
+}
+
+export default forwardRef(TextArea);
diff --git a/public/projects/react-small-apps/apps/notebook/src/components/commons/FormElements/index.js b/public/projects/react-small-apps/apps/notebook/src/components/commons/FormElements/index.js
new file mode 100644
index 0000000..1d1f610
--- /dev/null
+++ b/public/projects/react-small-apps/apps/notebook/src/components/commons/FormElements/index.js
@@ -0,0 +1,4 @@
+import Input from "./Input/Input";
+import TextArea from "./TextArea/TextArea";
+
+export { Input, TextArea };
diff --git a/public/projects/react-small-apps/apps/notebook/src/components/commons/List/List.css b/public/projects/react-small-apps/apps/notebook/src/components/commons/List/List.css
new file mode 100644
index 0000000..ae897a5
--- /dev/null
+++ b/public/projects/react-small-apps/apps/notebook/src/components/commons/List/List.css
@@ -0,0 +1,3 @@
+.list__item + .list__item {
+ margin-top: 0.5rem;
+}
diff --git a/public/projects/react-small-apps/apps/notebook/src/components/commons/List/List.js b/public/projects/react-small-apps/apps/notebook/src/components/commons/List/List.js
new file mode 100644
index 0000000..631e6a5
--- /dev/null
+++ b/public/projects/react-small-apps/apps/notebook/src/components/commons/List/List.js
@@ -0,0 +1,25 @@
+import "./List.css";
+
+function List({ type = "ul", data = [], modifier = "" }) {
+ const classNames = modifier ? `list list--${modifier}` : "list";
+
+ const listItems = data.map((object) => {
+ return (
+ <li key={object.id} className="list__item">
+ {object.body}
+ </li>
+ );
+ });
+
+ return (
+ <>
+ {type === "ol" ? (
+ <ol className={classNames}>{listItems}</ol>
+ ) : (
+ <ul className={classNames}>{listItems}</ul>
+ )}
+ </>
+ );
+}
+
+export default List;
diff --git a/public/projects/react-small-apps/apps/notebook/src/components/commons/index.js b/public/projects/react-small-apps/apps/notebook/src/components/commons/index.js
new file mode 100644
index 0000000..f0ca17b
--- /dev/null
+++ b/public/projects/react-small-apps/apps/notebook/src/components/commons/index.js
@@ -0,0 +1,5 @@
+import Button from "./Button/Button";
+import { Input, TextArea } from "./FormElements";
+import List from "./List/List";
+
+export { Button, Input, List, TextArea };
diff --git a/public/projects/react-small-apps/apps/notebook/src/components/helpers/hooks/useToggle.js b/public/projects/react-small-apps/apps/notebook/src/components/helpers/hooks/useToggle.js
new file mode 100644
index 0000000..0291324
--- /dev/null
+++ b/public/projects/react-small-apps/apps/notebook/src/components/helpers/hooks/useToggle.js
@@ -0,0 +1,10 @@
+import { useCallback, useState } from "react";
+
+function useToggle(initialState = false) {
+ const [state, setState] = useState(initialState);
+ const toggle = useCallback(() => setState((state) => !state), []);
+
+ return [state, toggle];
+}
+
+export default useToggle;
diff --git a/public/projects/react-small-apps/apps/notebook/src/components/layout/Footer/Footer.css b/public/projects/react-small-apps/apps/notebook/src/components/layout/Footer/Footer.css
new file mode 100644
index 0000000..31db439
--- /dev/null
+++ b/public/projects/react-small-apps/apps/notebook/src/components/layout/Footer/Footer.css
@@ -0,0 +1,8 @@
+.footer {
+ padding: 0 0 1rem;
+}
+
+.footer__copyright {
+ font-size: 0.9rem;
+ text-align: center;
+}
diff --git a/public/projects/react-small-apps/apps/notebook/src/components/layout/Footer/Footer.js b/public/projects/react-small-apps/apps/notebook/src/components/layout/Footer/Footer.js
new file mode 100644
index 0000000..20a87f2
--- /dev/null
+++ b/public/projects/react-small-apps/apps/notebook/src/components/layout/Footer/Footer.js
@@ -0,0 +1,11 @@
+import "./Footer.css";
+
+function Footer() {
+ return (
+ <footer className="footer">
+ <p className="footer__copyright">Notebook. MIT 2021. Armand Philippot.</p>
+ </footer>
+ );
+}
+
+export default Footer;
diff --git a/public/projects/react-small-apps/apps/notebook/src/components/layout/Header/Header.css b/public/projects/react-small-apps/apps/notebook/src/components/layout/Header/Header.css
new file mode 100644
index 0000000..a413a4f
--- /dev/null
+++ b/public/projects/react-small-apps/apps/notebook/src/components/layout/Header/Header.css
@@ -0,0 +1,7 @@
+.header {
+ padding: 1rem 0 0;
+}
+
+.header__branding {
+ font-size: 2.5rem;
+}
diff --git a/public/projects/react-small-apps/apps/notebook/src/components/layout/Header/Header.js b/public/projects/react-small-apps/apps/notebook/src/components/layout/Header/Header.js
new file mode 100644
index 0000000..03757fa
--- /dev/null
+++ b/public/projects/react-small-apps/apps/notebook/src/components/layout/Header/Header.js
@@ -0,0 +1,12 @@
+import "./Header.css";
+
+function Header({ children }) {
+ return (
+ <header className="header">
+ <h1 className="header__branding">Notebook</h1>
+ {children}
+ </header>
+ );
+}
+
+export default Header;
diff --git a/public/projects/react-small-apps/apps/notebook/src/components/layout/Main/Main.css b/public/projects/react-small-apps/apps/notebook/src/components/layout/Main/Main.css
new file mode 100644
index 0000000..f6ee8fe
--- /dev/null
+++ b/public/projects/react-small-apps/apps/notebook/src/components/layout/Main/Main.css
@@ -0,0 +1,3 @@
+.main {
+ flex: 1;
+}
diff --git a/public/projects/react-small-apps/apps/notebook/src/components/layout/Main/Main.js b/public/projects/react-small-apps/apps/notebook/src/components/layout/Main/Main.js
new file mode 100644
index 0000000..23e7b9d
--- /dev/null
+++ b/public/projects/react-small-apps/apps/notebook/src/components/layout/Main/Main.js
@@ -0,0 +1,7 @@
+import "./Main.css";
+
+function Main({ children }) {
+ return <main className="main">{children}</main>;
+}
+
+export default Main;
diff --git a/public/projects/react-small-apps/apps/notebook/src/components/layout/Nav/Nav.css b/public/projects/react-small-apps/apps/notebook/src/components/layout/Nav/Nav.css
new file mode 100644
index 0000000..9f5f90c
--- /dev/null
+++ b/public/projects/react-small-apps/apps/notebook/src/components/layout/Nav/Nav.css
@@ -0,0 +1,65 @@
+.nav {
+ position: relative;
+ display: flex;
+ flex-flow: row wrap;
+ justify-content: center;
+ margin: 2rem auto 0;
+}
+
+.nav .list--nav {
+ list-style-type: none;
+ position: absolute;
+ bottom: 100%;
+ width: 80vw;
+ margin: 0;
+ padding: 1rem;
+ background: #fff;
+ border: 1px solid #ccc;
+}
+
+@media screen and (min-width: 1024px) {
+ .nav .list--nav {
+ width: 50%;
+ }
+}
+
+.nav__link {
+ background: #fff;
+ border: none;
+ color: hsl(212, 46%, 34%);
+ text-decoration: underline;
+ display: inline-block;
+ margin: 0 1px;
+ padding: 0.8rem 1rem;
+}
+
+.nav .list__link {
+ display: block;
+ padding: 0.2rem;
+}
+
+.nav .list__link--current {
+ background: hsl(212, 46%, 34%);
+ color: #fff;
+}
+
+.nav__link:hover,
+.nav__link:focus,
+.nav .list__link:hover,
+.nav .list__link:focus {
+ text-decoration-thickness: 4px;
+}
+
+.nav__link:focus,
+.nav .list__link:focus {
+ color: inherit;
+}
+
+.nav .list__link--current:focus {
+ color: hsl(0, 0%, 89%);
+}
+
+.nav__link:active {
+ color: hsl(212, 46%, 20%);
+ text-decoration-thickness: 2px;
+}
diff --git a/public/projects/react-small-apps/apps/notebook/src/components/layout/Nav/Nav.js b/public/projects/react-small-apps/apps/notebook/src/components/layout/Nav/Nav.js
new file mode 100644
index 0000000..4e2a916
--- /dev/null
+++ b/public/projects/react-small-apps/apps/notebook/src/components/layout/Nav/Nav.js
@@ -0,0 +1,62 @@
+import { useEffect, useState } from "react";
+import { Link } from "react-router-dom";
+import { Button } from "../../commons";
+import NavJump from "./NavJump";
+import "./Nav.css";
+
+function Nav({ pages, currentPage, addNewPage, isPageExists }) {
+ const [isJumpEnabled, setIsJumpEnabled] = useState(false);
+
+ const isCover = () => currentPage && currentPage.id === 0;
+ const isFirstPage = () => currentPage && currentPage.id === 1;
+ const is404 = () => currentPage && currentPage.id === null;
+
+ useEffect(() => {
+ setIsJumpEnabled(false);
+ }, [currentPage.id]);
+
+ return (
+ <nav
+ className="nav"
+ onBlur={(e) => !e.relatedTarget && setIsJumpEnabled(false)}
+ >
+ {!isCover() && (
+ <Link className="nav__link" to="/" onClick={(e) => e.target.blur()}>
+ Back to cover
+ </Link>
+ )}
+ {!isCover() && !isFirstPage() && isPageExists(currentPage.id - 1) && (
+ <Link
+ className="nav__link"
+ to={`/page/${currentPage.id - 1}`}
+ onFocus={() => setIsJumpEnabled(false)}
+ onClick={(e) => e.target.blur()}
+ >
+ Previous page
+ </Link>
+ )}
+ <Button
+ additionalClassnames="nav__link"
+ onClickHandler={() => setIsJumpEnabled(!isJumpEnabled)}
+ >
+ Jump to
+ </Button>
+ {isJumpEnabled && <NavJump pages={pages} />}
+ {!is404() && (
+ <Link
+ className="nav__link"
+ to={`/page/${currentPage.id + 1}`}
+ onClick={(e) => {
+ !isPageExists(currentPage.id + 1) && addNewPage();
+ e.target.blur();
+ }}
+ onFocus={() => setIsJumpEnabled(false)}
+ >
+ Next page
+ </Link>
+ )}
+ </nav>
+ );
+}
+
+export default Nav;
diff --git a/public/projects/react-small-apps/apps/notebook/src/components/layout/Nav/NavJump.js b/public/projects/react-small-apps/apps/notebook/src/components/layout/Nav/NavJump.js
new file mode 100644
index 0000000..9d2a049
--- /dev/null
+++ b/public/projects/react-small-apps/apps/notebook/src/components/layout/Nav/NavJump.js
@@ -0,0 +1,28 @@
+import { NavLink } from "react-router-dom";
+import { List } from "../../commons";
+
+function NavJump({ pages }) {
+ const links = pages
+ .filter((page) => page.id > 0)
+ .map((page) => {
+ return {
+ id: page.id,
+ body: (
+ <NavLink
+ key={page.id}
+ className={({ isActive }) =>
+ isActive ? "list__link--current" : "list__link"
+ }
+ aria-current="page"
+ to={page.url}
+ >
+ {page.title}
+ </NavLink>
+ ),
+ };
+ });
+
+ return <List data={links} modifier="nav" />;
+}
+
+export default NavJump;
diff --git a/public/projects/react-small-apps/apps/notebook/src/components/layout/Page/Cover.css b/public/projects/react-small-apps/apps/notebook/src/components/layout/Page/Cover.css
new file mode 100644
index 0000000..bf915dd
--- /dev/null
+++ b/public/projects/react-small-apps/apps/notebook/src/components/layout/Page/Cover.css
@@ -0,0 +1,30 @@
+.notebook--cover .notebook-page--mirror {
+ display: none;
+}
+
+.notebook--cover .notebook-page {
+ grid-column: 1 / -1;
+ background: hsl(0, 4%, 20%);
+ color: rgb(234, 235, 236);
+ letter-spacing: 1px;
+ text-shadow: 1px 1px 0 #000;
+ box-shadow: none;
+}
+
+.notebook--cover .notebook-page {
+ justify-content: center;
+ text-align: center;
+}
+
+.notebook--cover .notebook-page__title {
+ font-size: 3rem;
+ font-weight: 600;
+}
+
+.notebook--cover .notebook-page .notebook-page__content {
+ flex: 0;
+}
+
+.notebook--cover .notebook-page .notebook-page__title .form__input {
+ text-align: center;
+}
diff --git a/public/projects/react-small-apps/apps/notebook/src/components/layout/Page/Page.css b/public/projects/react-small-apps/apps/notebook/src/components/layout/Page/Page.css
new file mode 100644
index 0000000..873df47
--- /dev/null
+++ b/public/projects/react-small-apps/apps/notebook/src/components/layout/Page/Page.css
@@ -0,0 +1,83 @@
+.notebook-page {
+ display: flex;
+ flex-flow: column nowrap;
+ padding: clamp(1rem, 3vw, 2rem) 0;
+ background: #fff;
+ border: 1px solid #cacaca;
+ box-shadow: 1px 1px 0 0 #ebebeb, 1px 1px 0 1px #bebebe, 2px 2px 0 1px #ebebeb,
+ 2px 2px 0 2px #bebebe, 3px 3px 0 2px #ebebeb, 3px 3px 0 3px #bebebe;
+}
+
+.notebook-page--mirror {
+ box-shadow: -1px 1px 0 0 #ebebeb, -1px 1px 0 1px #bebebe,
+ -2px 2px 0 1px #ebebeb, -2px 2px 0 2px #bebebe, -3px 3px 0 2px #ebebeb,
+ -3px 3px 0 3px #bebebe;
+}
+
+@media screen and (max-width: 1023px) {
+ .notebook-page {
+ grid-column: 1/-1;
+ }
+ .notebook-page--mirror {
+ display: none;
+ }
+}
+
+.notebook-page__header,
+.notebook-page__footer,
+.notebook-page__content {
+ padding: 0 3rem 0 2rem;
+}
+
+.notebook-page__title {
+ font-size: 1.8rem;
+ font-weight: 600;
+ margin: 1rem 0 0;
+}
+
+.notebook-page__title .form__input {
+ border: none;
+ font-weight: inherit;
+ padding: 0;
+ width: 100%;
+}
+
+.notebook-page__title .form__input:focus {
+ outline: none;
+}
+
+.notebook-page__content {
+ flex: 1;
+ display: flex;
+ margin: clamp(1rem, 3vw, 2rem) 0 clamp(2rem, 3vw, 3rem);
+ white-space: pre-wrap;
+ word-break: break-all;
+ hyphens: auto;
+}
+
+.notebook-page__content .form__textarea {
+ border: none;
+ line-height: inherit;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ padding: 0;
+}
+
+.notebook-page__content .form__textarea:focus {
+ outline: none;
+ resize: none;
+}
+
+.notebook-page__footer {
+ text-align: right;
+}
+
+.notebook-page__toolbar {
+ display: flex;
+ flex-flow: row;
+ gap: 1rem;
+ position: absolute;
+ top: -4rem;
+ right: 1rem;
+}
diff --git a/public/projects/react-small-apps/apps/notebook/src/components/layout/Page/Page.js b/public/projects/react-small-apps/apps/notebook/src/components/layout/Page/Page.js
new file mode 100644
index 0000000..19e072c
--- /dev/null
+++ b/public/projects/react-small-apps/apps/notebook/src/components/layout/Page/Page.js
@@ -0,0 +1,107 @@
+import { useEffect, useRef } from "react";
+import { Input, TextArea } from "../../commons";
+import useToggle from "../../helpers/hooks/useToggle";
+import PageToolbar from "./PageToolbar";
+import "./Cover.css";
+import "./Page.css";
+
+function Page({ page, setPage, removePage, restorePage, deletedPages }) {
+ const [isTitleEditable, setIsTitleEditable] = useToggle();
+ const [isBodyEditable, setIsBodyEditable] = useToggle();
+ const inputRef = useRef(null);
+ const textareaRef = useRef(null);
+
+ const isCover = () => page && page.id === 0;
+ const is404 = () => page && page.id === null;
+
+ useEffect(() => {
+ inputRef.current && inputRef.current.focus();
+ textareaRef.current && textareaRef.current.focus();
+ });
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ };
+
+ const handleOnChange = (e) => {
+ let newValue = {};
+
+ switch (e.target.name) {
+ case "notebook-title":
+ newValue = { title: e.target.value };
+ break;
+ case "notebook-body":
+ newValue = { body: e.target.value };
+ break;
+ default:
+ break;
+ }
+
+ setPage((previous) => {
+ return { ...previous, ...newValue };
+ });
+ };
+
+ return (
+ <article
+ className={`notebook-page ${isCover() ? "notebook-page--cover" : ""}`}
+ >
+ <header className="notebook-page__header">
+ {!isTitleEditable && (
+ <h2
+ className="notebook-page__title"
+ onClick={() => {
+ if (!is404()) setIsTitleEditable();
+ }}
+ >
+ {page.title}
+ </h2>
+ )}
+ {isTitleEditable && (
+ <form className="notebook-page__title" onSubmit={handleSubmit}>
+ <Input
+ ref={inputRef}
+ name="notebook-title"
+ value={page.title}
+ onChangeHandler={handleOnChange}
+ onBlurHandler={setIsTitleEditable}
+ />
+ </form>
+ )}
+ </header>
+ {!isBodyEditable && (
+ <div
+ className="notebook-page__content"
+ onClick={() => {
+ if (!is404()) setIsBodyEditable();
+ }}
+ >
+ {page.body}
+ </div>
+ )}
+ {isBodyEditable && (
+ <form className="notebook-page__content" onSubmit={handleSubmit}>
+ <TextArea
+ ref={textareaRef}
+ name="notebook-body"
+ value={page.body}
+ onChangeHandler={handleOnChange}
+ onBlurHandler={setIsBodyEditable}
+ />
+ </form>
+ )}
+ <footer className="notebook-page__footer">
+ {!isCover() && <div className="notebook-page__number">{page.id}</div>}
+ {!isCover() && (
+ <PageToolbar
+ removePage={removePage}
+ restorePage={restorePage}
+ deletedPages={deletedPages}
+ />
+ )}
+ </footer>
+ </article>
+ );
+}
+
+export default Page;
diff --git a/public/projects/react-small-apps/apps/notebook/src/components/layout/Page/PageToolbar.js b/public/projects/react-small-apps/apps/notebook/src/components/layout/Page/PageToolbar.js
new file mode 100644
index 0000000..a16aa22
--- /dev/null
+++ b/public/projects/react-small-apps/apps/notebook/src/components/layout/Page/PageToolbar.js
@@ -0,0 +1,27 @@
+import { Button } from "../../commons";
+import { ReactComponent as TrashIcon } from "../../../images/trash.svg";
+import { ReactComponent as RestoreIcon } from "../../../images/restore.svg";
+
+function PageToolbar({ removePage, restorePage, deletedPages }) {
+ return (
+ <div className="notebook-page__toolbar toolbar">
+ <div className="toolbar__item">
+ {deletedPages.length > 0 && (
+ <Button modifier="restore" onClickHandler={restorePage}>
+ <RestoreIcon
+ title="Undo page deletion"
+ className="icon icon--restore"
+ />
+ </Button>
+ )}
+ </div>
+ <div className="toolbar__item">
+ <Button modifier="delete" onClickHandler={removePage}>
+ <TrashIcon title="Delete this page" className="icon icon--trash" />
+ </Button>
+ </div>
+ </div>
+ );
+}
+
+export default PageToolbar;
diff --git a/public/projects/react-small-apps/apps/notebook/src/components/layout/index.js b/public/projects/react-small-apps/apps/notebook/src/components/layout/index.js
new file mode 100644
index 0000000..1b8d583
--- /dev/null
+++ b/public/projects/react-small-apps/apps/notebook/src/components/layout/index.js
@@ -0,0 +1,7 @@
+import Footer from "./Footer/Footer";
+import Header from "./Header/Header";
+import Main from "./Main/Main";
+import Nav from "./Nav/Nav";
+import Page from "./Page/Page";
+
+export { Footer, Header, Main, Nav, Page };