aboutsummaryrefslogtreecommitdiffstats
path: root/public/projects/react-small-apps/apps/notebook/src/components/layout/Page
diff options
context:
space:
mode:
Diffstat (limited to 'public/projects/react-small-apps/apps/notebook/src/components/layout/Page')
-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
4 files changed, 247 insertions, 0 deletions
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;