feat: responsive view (#75)

* chore: add license

* feat: mobile view
This commit is contained in:
STEVEN 2022-06-19 11:32:49 +08:00 committed by GitHub
parent b96d78ed19
commit cd7000da70
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 149 additions and 119 deletions

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Memos
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -33,6 +33,5 @@
"tailwindcss": "^3.0.18", "tailwindcss": "^3.0.18",
"typescript": "^4.3.2", "typescript": "^4.3.2",
"vite": "^2.9.0" "vite": "^2.9.0"
}, }
"license": "MIT"
} }

View file

@ -150,11 +150,11 @@ const MemoCardDialog: React.FC<Props> = (props: Props) => {
{linkMemos.length > 0 ? ( {linkMemos.length > 0 ? (
<div className="linked-memos-wrapper"> <div className="linked-memos-wrapper">
<p className="normal-text">{linkMemos.length} related MEMO</p> <p className="normal-text">{linkMemos.length} related MEMO</p>
{linkMemos.map((m) => { {linkMemos.map((memo, index) => {
const rawtext = parseHtmlToRawText(formatMemoContent(m.content)).replaceAll("\n", " "); const rawtext = parseHtmlToRawText(formatMemoContent(memo.content)).replaceAll("\n", " ");
return ( return (
<div className="linked-memo-container" key={m.id} onClick={() => handleLinkedMemoClick(m)}> <div className="linked-memo-container" key={`${index}-${memo.id}`} onClick={() => handleLinkedMemoClick(memo)}>
<span className="time-text">{m.dateStr} </span> <span className="time-text">{memo.dateStr} </span>
{rawtext} {rawtext}
</div> </div>
); );
@ -164,11 +164,11 @@ const MemoCardDialog: React.FC<Props> = (props: Props) => {
{linkedMemos.length > 0 ? ( {linkedMemos.length > 0 ? (
<div className="linked-memos-wrapper"> <div className="linked-memos-wrapper">
<p className="normal-text">{linkedMemos.length} linked MEMO</p> <p className="normal-text">{linkedMemos.length} linked MEMO</p>
{linkedMemos.map((m) => { {linkedMemos.map((memo, index) => {
const rawtext = parseHtmlToRawText(formatMemoContent(m.content)).replaceAll("\n", " "); const rawtext = parseHtmlToRawText(formatMemoContent(memo.content)).replaceAll("\n", " ");
return ( return (
<div className="linked-memo-container" key={m.id} onClick={() => handleLinkedMemoClick(m)}> <div className="linked-memo-container" key={`${index}-${memo.id}`} onClick={() => handleLinkedMemoClick(memo)}>
<span className="time-text">{m.dateStr} </span> <span className="time-text">{memo.dateStr} </span>
{rawtext} {rawtext}
</div> </div>
); );

View file

@ -294,7 +294,7 @@ const MemoEditor: React.FC<Props> = () => {
/> />
<div <div
ref={tagSeletorRef} ref={tagSeletorRef}
className={`tag-list ${isTagSeletorShown && tags.length > 0 ? "" : "hidden"}`} className={`tag-list ${isTagSeletorShown && tags.length > 0 ? "" : "!hidden"}`}
onClick={handleTagSeletorClick} onClick={handleTagSeletorClick}
> >
{tags.map((t) => { {tags.map((t) => {

View file

@ -1,7 +1,8 @@
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { memoService, shortcutService } from "../services";
import { useAppSelector } from "../store"; import { useAppSelector } from "../store";
import SearchBar from "./SearchBar"; import SearchBar from "./SearchBar";
import { memoService, shortcutService } from "../services"; import { toggleSiderbar } from "./Sidebar";
import "../less/memos-header.less"; import "../less/memos-header.less";
let prevRequestTimestamp = Date.now(); let prevRequestTimestamp = Date.now();
@ -25,7 +26,7 @@ const MemosHeader: React.FC<Props> = () => {
} }
}, [query, shortcuts]); }, [query, shortcuts]);
const handleMemoTextClick = useCallback(() => { const handleTitleTextClick = useCallback(() => {
const now = Date.now(); const now = Date.now();
if (now - prevRequestTimestamp > 10 * 1000) { if (now - prevRequestTimestamp > 10 * 1000) {
prevRequestTimestamp = now; prevRequestTimestamp = now;
@ -37,8 +38,13 @@ const MemosHeader: React.FC<Props> = () => {
return ( return (
<div className="section-header-container memos-header-container"> <div className="section-header-container memos-header-container">
<div className="title-text" onClick={handleMemoTextClick}> <div className="title-container">
<span className="normal-text">{titleText}</span> <div className="action-btn" onClick={toggleSiderbar}>
<img src="/icons/menu.svg" className="icon-img" alt="" />
</div>
<span className="title-text" onClick={handleTitleTextClick}>
{titleText}
</span>
</div> </div>
<SearchBar /> <SearchBar />
</div> </div>

View file

@ -34,6 +34,7 @@ const SettingDialog: React.FC<Props> = (props: Props) => {
</button> </button>
<div className="section-selector-container"> <div className="section-selector-container">
<span className="section-title">Basic</span> <span className="section-title">Basic</span>
<div className="section-items-container">
<span <span
onClick={() => handleSectionSelectorItemClick("my-account")} onClick={() => handleSectionSelectorItemClick("my-account")}
className={`section-item ${state.selectedSection === "my-account" ? "selected" : ""}`} className={`section-item ${state.selectedSection === "my-account" ? "selected" : ""}`}
@ -46,15 +47,18 @@ const SettingDialog: React.FC<Props> = (props: Props) => {
> >
Preferences Preferences
</span> </span>
</div>
{user?.role === "OWNER" ? ( {user?.role === "OWNER" ? (
<> <>
<span className="section-title">Admin</span> <span className="section-title">Admin</span>
<div className="section-items-container">
<span <span
onClick={() => handleSectionSelectorItemClick("member")} onClick={() => handleSectionSelectorItemClick("member")}
className={`section-item ${state.selectedSection === "member" ? "selected" : ""}`} className={`section-item ${state.selectedSection === "member" ? "selected" : ""}`}
> >
Member Member
</span> </span>
</div>
</> </>
) : null} ) : null}
</div> </div>

View file

@ -27,6 +27,11 @@ const Sidebar: React.FC<Props> = () => {
return ( return (
<aside className="sidebar-wrapper"> <aside className="sidebar-wrapper">
<div className="close-container">
<span className="action-btn" onClick={toggleSiderbar}>
<img src="/icons/close.svg" className="icon-img" alt="" />
</span>
</div>
<UserBanner /> <UserBanner />
<div className="status-text-container"> <div className="status-text-container">
<div className="status-text memos-text"> <div className="status-text memos-text">
@ -57,4 +62,14 @@ const Sidebar: React.FC<Props> = () => {
); );
}; };
export const toggleSiderbar = () => {
const sidebarEl = document.body.querySelector(".sidebar-wrapper") as HTMLDivElement;
const display = window.getComputedStyle(sidebarEl).display;
if (display === "none") {
sidebarEl.style.display = "flex";
} else {
sidebarEl.style.display = "none";
}
};
export default Sidebar; export default Sidebar;

View file

@ -1,5 +1,7 @@
import axios from "axios"; import axios from "axios";
axios.defaults.withCredentials = true;
type ResponseObject<T> = { type ResponseObject<T> = {
data: T; data: T;
error?: string; error?: string;

View file

@ -1,6 +1,4 @@
@import "./mixin.less"; @import "./mixin.less";
#root { #root {
.flex(row, flex-start, flex-start);
@apply w-full h-full;
} }

View file

@ -1,31 +1,13 @@
@import "./mixin.less"; @import "./mixin.less";
* {
@apply m-0 p-0 box-border;
color: @text-black;
-webkit-tap-highlight-color: transparent;
}
body, body,
html { html {
@apply w-screen h-screen overflow-hidden text-base; @apply text-base;
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Noto Sans", "Noto Sans CJK SC", "Microsoft YaHei UI", "Microsoft YaHei", font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Noto Sans", "Noto Sans CJK SC", "Microsoft YaHei UI", "Microsoft YaHei",
"WenQuanYi Micro Hei", sans-serif, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "WenQuanYi Micro Hei", sans-serif, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
"Noto Color Emoji"; "Noto Color Emoji";
} }
code {
@apply bg-pink-600 p-1 rounded font-mono;
}
pre {
@apply font-mono;
* {
@apply font-mono;
}
}
label, label,
button, button,
img { img {
@ -57,17 +39,10 @@ li {
} }
a { a {
@apply cursor-pointer text-blue-600 underline underline-offset-2; @apply cursor-pointer text-blue-600 underline underline-offset-2 hover:opacity-80;
&:hover {
@apply opacity-80;
}
} }
.btn { .btn {
@apply select-none cursor-pointer text-center; @apply select-none cursor-pointer text-center;
} }
.hidden {
display: none !important;
}

View file

@ -1,15 +1,19 @@
@import "./mixin.less"; @import "./mixin.less";
@import "./memos-header.less";
#root { .page-wrapper.home {
@apply relative top-0 w-full h-screen overflow-y-auto;
background-color: #f6f5f4; background-color: #f6f5f4;
}
#page-wrapper { > .page-container {
@apply w-full h-full m-auto grid max-w-4xl mx-auto; @apply relative w-full min-h-screen mx-auto flex flex-row justify-center items-start;
grid-template-columns: min-content 1fr;
.memos-wrapper { >.sidebar-wrapper{
@apply w-full h-full overflow-x-hidden flex flex-col justify-start items-start px-4 pr-10; @apply flex-shrink-0;
}
> .memos-wrapper {
@apply relative w-full max-w-2xl min-h-full overflow-x-hidden flex flex-col justify-start items-start px-4 sm:pr-6;
}
} }
} }

View file

@ -1,12 +1,10 @@
@import "./mixin.less"; @import "./mixin.less";
.dialog-wrapper.memo-card-dialog { .dialog-wrapper.memo-card-dialog {
> .dialog-container { @apply px-4;
@apply p-0 bg-transparent;
> * { > .dialog-container {
@apply shrink-0; @apply w-full p-0 bg-transparent flex flex-col justify-start items-center;
}
> .memo-card-container { > .memo-card-container {
.flex(column, flex-start, flex-start); .flex(column, flex-start, flex-start);

View file

@ -1,7 +1,8 @@
@import "./mixin.less"; @import "./mixin.less";
@import "./memos-header.less";
.memo-trash-dialog { .memo-trash-dialog {
@apply px-4;
> .dialog-container { > .dialog-container {
@apply w-128 max-w-full mb-8; @apply w-128 max-w-full mb-8;

View file

@ -5,20 +5,22 @@
.flex(row, space-between, center); .flex(row, space-between, center);
@apply w-full h-10 flex-nowrap mt-4 mb-2 shrink-0; @apply w-full h-10 flex-nowrap mt-4 mb-2 shrink-0;
> .title-text { > .title-container {
.flex(row, flex-start, center); @apply flex flex-row justify-start items-center mr-2 shrink-0 overflow-hidden;
@apply font-bold text-lg leading-10 mr-2 text-ellipsis shrink-0 cursor-pointer overflow-hidden;
color: @text-black;
> .action-btn { > .action-btn {
.flex(row, center, center); @apply flex sm:hidden flex-row justify-center items-center w-6 h-6 mr-1 shrink-0;
@apply w-6 h-6 mr-1 shrink-0;
background-color: unset; background-color: unset;
> .icon-img { > .icon-img {
@apply w-4 h-auto; @apply w-5 h-auto;
} }
} }
> .title-text {
@apply font-bold text-lg leading-10 mr-2 text-ellipsis shrink-0 cursor-pointer overflow-hidden;
color: @text-black;
}
} }
> .btns-container { > .btns-container {

View file

@ -2,22 +2,14 @@
.menu-btns-popup { .menu-btns-popup {
.flex(column, flex-start, flex-start); .flex(column, flex-start, flex-start);
@apply absolute mt-1 ml-24 p-1 w-44 rounded-lg z-10 shadow bg-white; @apply absolute right-0 top-0 mt-1 p-1 w-36 rounded-lg z-10 shadow bg-white;
&:hover {
display: flex;
}
> .btn { > .btn {
.flex(row, flex-start, center); .flex(row, flex-start, center);
@apply w-full py-2 px-3 text-base rounded text-left; @apply w-full py-2 px-3 text-base rounded text-left hover:bg-gray-100;
> .icon { > .icon {
@apply block w-6 text-center mr-2 text-base; @apply block w-6 text-center mr-2 text-base;
} }
&:hover {
background-color: @bg-whitegray;
}
} }
} }

View file

@ -15,10 +15,6 @@
@bg-light-blue: #eef3fe; @bg-light-blue: #eef3fe;
@bg-paper-yellow: #fbf4de; @bg-paper-yellow: #fbf4de;
.mono-font-family {
font-family: SFMono-Regular, Menlo, Consolas, "PT Mono", "Liberation Mono", Courier, monospace;
}
.hide-scroll-bar { .hide-scroll-bar {
.pretty-scroll-bar(0, 0); .pretty-scroll-bar(0, 0);

View file

@ -23,8 +23,7 @@
} }
> .quickly-action-wrapper { > .quickly-action-wrapper {
@apply hidden absolute top-9 -right-2 p-2 w-80; @apply hidden absolute top-9 -right-2 p-2 w-80 z-10;
z-index: 2;
> .quickly-action-container { > .quickly-action-container {
.flex(column, flex-start, flex-start); .flex(column, flex-start, flex-start);

View file

@ -1,13 +1,14 @@
@import "./mixin.less"; @import "./mixin.less";
@import "./memos-header.less";
.setting-dialog { .setting-dialog {
@apply px-4;
> .dialog-container { > .dialog-container {
@apply w-176 max-w-full mb-8 p-0; @apply w-176 max-w-full mb-8 p-0;
> .dialog-content-container { > .dialog-content-container {
.flex(column, flex-start, flex-start); .flex(column, flex-start, flex-start);
@apply relative w-full overflow-y-scroll p-0 flex flex-row justify-start items-start; @apply relative w-full overflow-y-scroll p-0 flex flex-col sm:flex-row justify-start items-start;
.hide-scroll-bar(); .hide-scroll-bar();
> .close-btn { > .close-btn {
@ -20,14 +21,17 @@
} }
> .section-selector-container { > .section-selector-container {
@apply w-40 h-full shrink-0 rounded-l-lg p-4 border-r bg-gray-100 flex flex-col justify-start items-start; @apply w-full sm:w-40 h-auto sm:h-full shrink-0 rounded-t-lg sm:rounded-l-lg p-4 border-r bg-gray-100 flex flex-col justify-start items-start;
> .section-title { > .section-title {
@apply text-sm mt-4 first:mt-3 mb-1 font-mono text-gray-400; @apply text-sm mt-4 first:mt-3 mb-1 font-mono text-gray-400;
} }
>.section-items-container{
@apply w-full h-auto flex flex-row sm:flex-col justify-start items-start;
> .section-item { > .section-item {
@apply text-base left-6 mt-2 text-gray-700 cursor-pointer hover:opacity-80; @apply text-base mr-2 sm:mr-0 mt-2 text-gray-700 cursor-pointer hover:opacity-80;
&.selected { &.selected {
@apply font-bold hover:opacity-100; @apply font-bold hover:opacity-100;
@ -35,8 +39,10 @@
} }
} }
}
> .section-content-container { > .section-content-container {
@apply w-auto p-4 px-6 grow flex flex-col justify-start items-start h-128 overflow-y-scroll; @apply w-full sm:w-auto p-4 px-6 grow flex flex-col justify-start items-start h-128 overflow-y-scroll;
> .section-container { > .section-container {
.flex(column, flex-start, flex-start); .flex(column, flex-start, flex-start);
@ -47,8 +53,7 @@
} }
> .form-label { > .form-label {
.flex(row, flex-start, center); @apply flex flex-row justify-start items-center w-full mb-2;
@apply w-full mb-2;
> .normal-text { > .normal-text {
@apply shrink-0 select-text; @apply shrink-0 select-text;

View file

@ -9,12 +9,14 @@
} }
&.username-label { &.username-label {
@apply w-full flex-wrap;
> input { > input {
@apply grow-0 shadow-inner w-auto px-2 py-1 text-base border rounded leading-6 bg-transparent focus:border-black; @apply grow-0 shadow-inner w-auto px-2 py-1 mr-2 text-base border rounded leading-6 bg-transparent focus:border-black;
} }
> .btns-container { > .btns-container {
@apply ml-2 shrink-0 flex flex-row justify-start items-center; @apply mr-2 shrink-0 flex flex-row justify-start items-center;
> .btn { > .btn {
@apply text-sm shadow px-4 py-1 leading-6 rounded border hover:opacity-80 bg-gray-50; @apply text-sm shadow px-4 py-1 leading-6 rounded border hover:opacity-80 bg-gray-50;

View file

@ -1,10 +1,21 @@
@import "./mixin.less"; @import "./mixin.less";
.sidebar-wrapper { .sidebar-wrapper {
.flex(column, flex-start, flex-start); @apply fixed sm:sticky top-0 left-0 hidden sm:!flex flex-col justify-start items-start w-64 h-screen py-4 pl-2 z-10 bg-white sm:bg-transparent shadow-2xl sm:shadow-none overflow-x-hidden overflow-y-auto transition-all;
@apply w-64 h-full py-4 pl-2 overflow-x-hidden overflow-y-auto;
.hide-scroll-bar(); .hide-scroll-bar();
> .close-container {
@apply w-full pr-6 my-2 flex sm:hidden flex-row justify-end items-center;
> .action-btn {
@apply p-1 bg-gray-100 rounded shadow;
> .icon-img {
@apply w-4 h-auto;
}
}
}
> .action-btns-container { > .action-btns-container {
@apply w-full px-2 my-2 flex flex-col justify-start items-start shrink-0; @apply w-full px-2 my-2 flex flex-col justify-start items-start shrink-0;

View file

@ -2,7 +2,7 @@
.page-wrapper.signin { .page-wrapper.signin {
.flex(row, center, center); .flex(row, center, center);
@apply w-full h-full bg-white; @apply w-full min-h-screen bg-white;
> .page-container { > .page-container {
@apply w-80 max-w-full py-4 -mt-16; @apply w-80 max-w-full py-4 -mt-16;

View file

@ -32,9 +32,9 @@ function Home() {
}, []); }, []);
return ( return (
<> <section className="page-wrapper home">
{loadingState.isLoading ? null : ( {loadingState.isLoading ? null : (
<section id="page-wrapper"> <div className="page-container">
<Sidebar /> <Sidebar />
<main className="memos-wrapper"> <main className="memos-wrapper">
<MemosHeader /> <MemosHeader />
@ -42,9 +42,9 @@ function Home() {
<MemoFilter /> <MemoFilter />
<MemoList /> <MemoList />
</main> </main>
</section> </div>
)} )}
</> </section>
); );
} }

View file

@ -16,5 +16,5 @@
"noEmit": true, "noEmit": true,
"jsx": "react-jsx" "jsx": "react-jsx"
}, },
"include": ["./src", "./public"] "include": ["./src"]
} }